Newfound love of @staticmethod in Python

02 July 2012   4 comments   Python

Mind That Age!

This blog post is 5 years old! Most likely, its content is outdated. Especially if it's technical.

Powered by Fusion×

The @staticmethod decorator is nothing new. In fact, it was added in version 2.2. However, it's not till now in 2012 that I have genuinely fallen in love with it.

First a quick recap to remind you how @staticmethod works.

class Printer(object):

    def __init__(self, text):
        self.text = text

    def newlines(s):
        return s.replace('\n','\r')

    def printer(self):
        return self.newlines(self.text)

p = Printer('\n\r')
assert p.printer() == '\r\r'

So, it's a function that has nothing to do with the instance but still belongs to the class. It belongs to the class from an structural point of view of the observer. Like, clearly the newlines function is related to the Printer class. The alternative is:

def newlines(s):
    return s.replace('\n','\r')

class Printer(object):

    def __init__(self, text):
        self.text = text

    def printer(self):
        return newlines(self.text)

p = Printer('\n\r')
assert p.printer() == '\r\r'

It's the exact same thing and one could argue that the function has nothing to do with the Printer class. But ask yourself (by looking at your code); how many times do you have classes with methods on them that take self as a parameter but never actually use it?

So, now for the trump card that makes it worth the effort of making it a staticmethod: object orientation. How would you do this neatly without OO?

class UNIXPrinter(Printer):

    def newlines(s):
        return s.replace('\n\r', '\n')

p = UNIXPrinter('\n\r')
assert p.printer() == '\n'  

Can you see it? It's ideal for little functions that should be domesticated by the class but have nothing to do with the instance (e.g. self). I used to think it looked like it's making a pure looking thing like something more complex that it needs to be. But now, I think it looks great!


Consider using classmethod instead. Guido has indicated that staticmethod is the result of a misunderstanding and he'd take it back if he could.
Adam Skutt
Certainly by passing the conversion function?

def newlines(s):
     return s.replace('\n', '\r')

class Printer(object):
     def __init__(self, text, to_newline=newlines):
          self.text = text
          self.to_newline = to_newline

     def printer(self):
          return self.to_newline(self.text)

def _to_unix_newline(s):
    return s.replace('\r\n', '\n')

def UNIXPrinter(text):
    return Printer(text, _to_unix_newline)

Yes, the fact that you can call a static method through an instance in Python means you get "virtual static" methods. I'm not sure that's an especially compelling use for them. Consider the case where UNIXPrinter is-a Printer, but provides a definition of the printer method that doesn't call the newlines static method. Unless UNIXPrinter provides the unneeded newlines staticmethod anyway, a caller rationally expecting newlines to perform '\r\n' -> '\n' conversion will be awfully surprised when it does not.

That is not to say such an approach never makes sense, but you've increased the maintenance burden on all of your child classes in support of one specific implementation of the printer method. That's a violation of the open/closed principal.

A good use of @classmethod/@staticmethod is when someone is naturally supplied a type (instead of an instance), and they need to perform useful operations on the type. This is common with plugins: you may have a list / dict of registered plugin types, and each type has a static/class method "add_options" that adds command-line options to an argparse / optparse instance, so the plugin can manipulate the command-line. They're necessary in this example because:
1) It's (typically) silly to instantiate an instance of the Plugin just to do command-line parsing. The Plugin shouldn't be instantiated until there is useful work to do.
2) The Plugin class must necessarily provide the entire interface between the main application and the plugin. If it doesn't exist on the Plugin class, the main application cannot call it.
and don't forget @classmethod that will pass you the class it's invoked on as first argument (usually named 'cls')
Erik Rose
In addition to the philosophical satisfaction from having a related "bag of functions", static methods are better than module-level functions because subclasses can override them.
Thank you for posting a comment

Your email will never ever be published

Related posts

Difference between $.data('foo') and $.attr('data-foo') in jQuery 10 June 2012
US License Plate Spotter (part 1) 09 July 2012
Related by Keyword:
Public Class Fields saves sooo many keystrokes in React code 14 April 2017
4 different kinds of React component styles 07 April 2016
Welcome to the world django-fancy-cache! 01 March 2013
Secs sell! How I cache my entire pages (server-side) 10 May 2012
Eloquent Javascript by Marijn Haverbeke 25 February 2011
Related by Text:
__call__ folderish Zope objects 19 December 2004
Tip: Printer friendly pages with Page Templates in Zope 24 March 2008
How to unit test the innards of a Django view function 15 November 2008
Python new-style classes and the super() function 12 July 2008
Python file with closing automatically 03 December 2011