Kaushik's Blog

Static methods in Python, yay or nay?

Here are some code review comments of mine that share a specific theme:

April 30, 2024 at 1:42:33 PM EDT

Suggest making this a staticmethod since it's not a stateful operation; it just takes some arguments and packages them into a new message. That would, in turn, make this easier to test. Especially if more attributes need to be packaged into the message.

May 15, 2024 at 11:12:43 AM EDT

As you can see, I'm a huge fan of static methods for small, well-defined functionality. The fewer methods that have access to the state variables of the class, the better! It also helps to avoid unprecedented errors from accidentally mutating state where it's not supposed to be.

June 20, 2024 at 3:55:11 PM EDT

I think this particular problem is a result of these methods using self.* variables instead of explicitly taking the arguments they need. In general, it's preferable to explicitly take arguments (pointcloud_msg, pickpoint_msg) rather than relying on state variables (self.pointcloud_msg, self.pickpoint_msg). Even better is, of course, static methods when practicable.

Only today, after I wrote that most recent comment, did I wonder whether static methods were, "of course", even better.

In search of an answer, I cracked open the large tome that is Fluent Python and went straight to page 372, to the section classmethod Versus staticmethod. Sure enough, there's an opposing view right there, beginning with

...good use cases for staticmethod are very rare in my experience.

A static method doesn't receive a self argument. It's essentially no different from a free function. It takes zero or more arguments, does zero or more things with them, and returns zero or more results.

Methods that take a self parameter (also called bound methods), typically use members of the object to do things. Moreover, since bound methods are objects too, every instance of a class contains, within it, an instance of each of its bound methods. Bound methods don't come for free. That's why, if a method doesn't use any of the object's members, it probably shouldn't be bound to the object.

Where does a method like that belong? Placing such a method inside a class indicates that the function is somewhat closely related to the class. Still, it's hardly necessary to put it inside a class to make that evident. Placing the function inside the same module would indicate the same close relationship.

I think that's my conclusion: a function that doesn't use any attributes of an object shouldn't be static; it should be a free function in the same module.

The only convincing reason to use a staticmethod is, maybe, in an inheritance hierarchy. I found this example here:

class Pizza(object):
   @staticmethod
   def mix_ingredients(x, y):
      return x + y

   def cook(self):
      return self.mix_ingredients(self.cheese, self.vegetables)

The argument is that making mix_ingredients a static method would allow a subclass of Pizza, that needed to mix ingredients in a not-so-additive way, to simply override the mix_ingredients function. If mix_ingredients were not part of the class, the SubPizza would have to override the entire cook function.

I was convinced when I first read this, but I'm reevaluating that thought. This seems like a problem of interfaces. The behaviour of the Pizza class is to additively mix its ingredients; that is a part of its business logic, as captured in its cook function. The logic of a SubPizza class might be different, which would be borne out in its cook() function. mix_ingredients feels like a red herring here; it's not representative of a real use case for inheritance.

If I were to rewrite the above example this way, without the static method, the argument would be moot.

class Pizza(object):
   def cook(self):
      return self.cheese + self.vegetables

Clearly, the Pizza's cook function is its way of cooking, its own business logic. A SubPizza may do it a different way with its members.

Perhaps I will reconsider my opinion the next time I write a code review comment. But as of today, I'm no longer on the static method bandwagon (at least in Python).

#python