The python builtin staticmethod has been one of those language features that I understood, but couldn’t figure out why I would ever use it. When I did my PyOhio talk on decorators, I asked if anyone in the room could explain why to use it. People came up with these ideas:
- Put associated functions inside the relevant class, so we don’t pollute the module namespace
- Make java programmers feel less homesick
Both of these are valid, I guess, but in my mind, it just confirmed that I would never use staticmethod.
Then I watched this neat video. You don’t have to go watch that video before you read the next section. I just wanted to point to where I picked up this trick.
Remember that any function bound to a class gets called with an extra parameter inserted at the beginning. That parameter is a reference to the instance of the class. So, you can’t do this:
>>> def f(a, b):
... return a - b
>>> class C(object):
>>> C.f = f
>>> c = C()
>>> c.f(3, 1)
Traceback (most recent call last):
TypeError: f() takes exactly 2 arguments (3 given)
The call to f blew up because f only takes two parameters, but it got three parameters instead because python automatically adds on the self parameter at the beginning. So f really got called like this: f(self, 3, 1)
Now the reason for staticmethod becomes obvious. If I want to allow instances of my class C to call function f, I have to make f a staticmethod on C, so that the python plumbing won’t insert that extra first argument at the beginning.
So, I’ll overwrite the first C.f method with a static method, and then call c.f again:
>>> C.f = staticmethod(f)
>>> c.f(3, 1) # This is the same already-instantiated c from earlier.
As a side effect of watching that video, I think about dependency injection (DI) differently now. In the video, Thomas Woulters says he uses staticmethod for dependency injection. This kind of DI is a different approach than what I’ve seen before. Most everybody that talks about DI emphasizes passing in lots and lots of crap into the __init__ method or in any subsequent methods, so every component can be mocked out.
But it makes just as much sense to graft on dependencies to the class outside of the __init__ method too, since the language supports that. The end goal is still reached. Code can still tweak the dependencies and provide alternative objects, and at the same time, I don’t end up with ridiculously long function signatures.