statslink

linking statistics to all

Some Python interview questions (Part II)

In Part I we talked about three technical interview questions I encountered as a data scientist.

  1. What is the difference between *args and *kwargs?
  2. What is a decorator?
  3. What is iterrows()used for?

We answered the first question, now let’s talk about what a decorator is (we can use function and class interchangeably). According to ChatGPT:


A decorator allows you to modify the behavior of a function or a class. It is a higher-order function that takes another function as an argument and returns a modified function. Decorators call higher-order functions, which is a function that takes one or more functions as arguments or returns one or more functions.

The primary use of decorators is to extend or alter the behavior of the decorated function or class without permanently modifying it. Decorators can be applied to any callable in Python, meaning they can decorate functions (think of it as a standalone method that’s not in a class), methods (a function defined in a class), or classes (consists of objects and methods).

In the code below, we define a decorator function called @add_buns that adds prints the top and bottom buns for our veggie patty. We pass in myfunc, which is the function we want to add the buns to. Next we define the wrapper function to sandwich the myfunc in between two buns. The Python convention is to call this function wrapper so as to know we are ‘wrapping’ the decorator (did you get the gift analogy?) around our function. We can now decorate as many functions as we want. I’m vegetarian but let’s start with a meat burger for all you carnivores, let’s add buns to make_meat_burger and then I’ll decorate my own make_veggie_burger. Which burger would you like?

We just demonstrated the main purpose of the decorator, so that we can do the same thing, add_buns to as many burger functions as we want! This reduces repetitive coding.

#define decorator function
def add_buns(myfunc):
    def wrapper():
        print("---Top Bun---")
        myfunc()
        print("--Bottom Bun--")
    return wrapper

#decorate our meat burger
@add_buns
def make_meat_burger():
    print("Meat Patty")

#decorate my veggie burger
@add_buns
def make_veggie_burger():
    print("Veggie Patty")

#call the two functions
make_meat_burger()
make_veggie_burger()

### output ###
---Top Bun---
Meat Patty
--Bottom Bun--
---Top Bun---
Veggie Patty
--Bottom Bun--

Finally, decorators are known as ‘syntactic sugar’, which means making code prettier without changing its functionality. There is actually a more traditional syntax which involves passing the new function into add_buns as an argument. Suppose instead of a veggie burger we want to add buns to a potato patty, let’s call our new function make_potato_burger. However, instead of putting @add_buns on top of this function, we simply define it and pass that function into add_buns. We don’t have to change anything about add_buns itself, just how we apply it. Recall that in the original definition of add_buns we returned the wrapper function, which is why when we assign add_buns_to_potato_burger to the results of the decorator function it also becomes a function. In general, assigning an object to a function does not make it a function unless you explicitly return a function.

def make_potato_burger():
  print("Potato Patty")

add_buns_to_potato_burger = add_buns(make_potato_burger)
add_buns_to_potato_burger()

### output ###
---Top Bun---
Potato Patty
--Bottom Bun--

There is special method in Python that uses the decorator @ symbol but is conceptually different from a decorator, the bult-in @staticmethod. Unlike regular (instance) methods, static methods do not receive self as an argument, so they are independent of (and cannot alter) the instance of a class. Static methods are useful for math functions such as sine or cosine because their behavior is the same from instance to instance of a class (hence the name static). Another special method that uses the decorator symbol is @classmethod that receives the class itself rather than an instance of a class, so it passes in cls instead of self as an argument.

In the next post, I will answer the last question about iterrows() in pandas, and in a future post I will discuss the difference between functions, methods, and classes.