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 “@”.
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 argumentinstance
: 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.
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:
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.
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!
Bonus: What about the classmethod decorator?
Coding the classmethod
decorator is very similar to staticmethod
! The only difference is that we need to:
- Retrieve the class the decorated method belongs to.
- Bound the decorated method to its class (and not to an object) which is the argument owner of
__get__
.
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!