April 7, 2022 • 2 min read

Under the hood of staticmethod: let’s recode this decorator!

Rédigé par Noé Achache

Noé Achache

As developers, there are tools we use every day but never really think about. A good example in Python is @staticmethod. So let's dig into what it really is, and re-code this decorator in pure Python.

Beyond explaining the code behind staticmethod, the purpose of this article is to explain some key Pythonic concepts such as descriptors, bound methods, and decorators (especially class decorators).

What is a Static Method?

Methods usually have an implicit argument called self by convention for classic methods, and cls for class methods. A static method is a method that does not have this implicit argument.

@staticmethod is a decorator, that gives the decorated method a static behavior. A decorator is simply a wrapper using the syntactic sugar “@”.

staticmethod example

In the following sections, we are going to dive into the three main underlying concepts of static methods: descriptors, bound methods, and class decorators

Methods are Descriptors

Descriptors are Python objects, used as attributes of other classes, that implement a method of the descriptor protocol  (from Real Python) :

  • __get__
  • __set__
  • __delete__

These magic methods allow controlling the behavior of the descriptor by respectively getting, setting, or deleting the attribute of the object. A common example of descriptors is properties.

Methods are descriptors! More specifically, they are non-data descriptors, meaning they implement __get__, and neither __set__ nor __delete__. Under the hood, when a method (let’s say my_method) is called, Python automatically returns my_method.__get__(*args) where the args depend on the context of the method. Indeed, __get__ takes three successive arguments:

  • self: implicit argument
  • instance: The instance of the owner class (c.f. owner below).
  • owner: Owner class, in which the descriptor is used. In the case of a method, the owner is the class the method belongs to.

and it returns:

  • The attribute of the descriptor. The __get__ of a method, returns the method itself, bound to its object (if called from an object), as explained in the next section.

Bound vs Unbound Methods

When calling a method of an object, a reference to the object is passed as the first argument of the method (the implicit self). This is because the method is bound to the object.

Bound vs Unbound methods’ representation

More precisely, when the method is called, the object is passed as the instance argument of __get__, which bounds the method to the object. A few examples to get a better grasp on it:

Different ways to pass dummy_class_instance as the first argument of my_method

As you probably guessed, static methods are unbound methods!

Class Decorators

Decorators are generally functions, that wrap the decorated functions. However, a decorator can also be a class, which allows for more in-depth control over the decorated function.

Indeed, a class decorator can implement the magic method __get__. As a result, it is the __get__ of the decorator and not the one of the decorated method which is called when calling the method.

Another interesting behavior of decorators is that they are processed when the class’ code is first interpreted and not when instantiating an object or calling the method. Hence, the decorated method is naturally unbound, as no object has been created yet.

Class decorator example

Putting it all together

From what we learned so far, a class decorator can retrieve the method to make static, before it is bound to an object. Moreover, it can implement __get__ and have it return the original unbound method, whatever the instance argument is. As a result, even when calling the method from an object, the method remains unbound!

Recoding the staticmethod decorator with example

Bonus: What about the classmethod decorator?

Coding the classmethod decorator is very similar to staticmethod! The only difference is that we need to:

  1. Retrieve the class the decorated method belongs to.
  2. Bound the decorated method to its class (and not to an object) which is the argument owner of __get__.
Recoding the classmethod decorator

Conclusion

Methods are descriptors and hence implement the magic method __get__. @staticmethod is a class decorator, reimplementing __get__ and making it return a version of the decorated method that has not been bound to the object it belongs to. Hence, the decorated method does not have the implicit argument self.

NB: Like most native Python objects, staticmethod is actually coded in C.

If you are still striving for more Pythonic knowledge, have a look at this article on zen of python !

Are you looking for Data Engineer Experts? Don't hesitate to contact us!

Cet article a été écrit par

Noé Achache

Noé Achache