Python's @classmethod and @staticmethod Explained

Python is a unique language in that it is fairly easy to learn, given its straight-forward syntax, yet still extremely powerful. There are a lot more features under the hood than you might realize. While I could be referring to quite a few different things with this statement, in this case I'm talking about the decorators @classmethod and @staticmethod. For many of your projects, you probably didn't need or encounter these features, but you may find that they come in handy quite a bit more than you'd expect. It's not as obvious how to create Python static methods, which is where these two decorators come in.

In this article I'll be telling you what each of these decorators do, their differences, and some examples of each.

The @classmethod Decorator

This decorator exists so you can create class methods that are passed the actual class object within the function call, much like self is passed to any other ordinary instance method in a class.

In those instance methods, the self argument is the class instance object itself, which can then be used to act on instance data. @classmethod methods also have a mandatory first argument, but this argument isn't a class instance, it's actually the uninstantiated class itself. So, while a typical class method might look like this:

class Student(object):

    def __init__(self, first_name, last_name):
        self.first_name = first_name
        self.last_name = last_name

scott = Student('Scott',  'Robinson')  

A similar @classmethod method would be used like this instead:

class Student(object):

    @classmethod
    def from_string(cls, name_str):
        first_name, last_name = map(str, name_str.split(' '))
        student = cls(first_name, last_name)
        return student

scott = Student.from_string('Scott Robinson')  

This follows the static factory pattern very well, encapsulating the parsing logic inside of the method itself.

The above example is a very simple one, but you can imagine more complicated examples that make this more attractive. Imagine if a Student object could be serialized in to many different formats. You could use this same strategy to parse them all:

class Student(object):

    @classmethod
    def from_string(cls, name_str):
        first_name, last_name = map(str, name_str.split(' '))
        student = cls(first_name, last_name)
        return student

    @classmethod
    def from_json(cls, json_obj):
        # parse json...
        return student

    @classmethod
    def from_pickle(cls, pickle_file):
        # load pickle file...
        return student

The decorator becomes even more useful when you realize its usefulness in sub-classes. Since the class object is given to you within the method, you can still use the same @classmethod for sub-classes as well.

The @staticmethod Decorator

The @staticmethod decorator is similar to @classmethod in that it can be called from an uninstantiated class object, although in this case there is no cls parameter passed to its method. So an example might look like this:

class Student(object):

    @staticmethod
    def is_full_name(name_str):
        names = name_str.split(' ')
        return len(names) > 1

Student.is_full_name('Scott Robinson')   # True  
Student.is_full_name('Scott')            # False  

Since no self object is passed either, that means we also don't have access to any instance data, and thus this method can not be called on an instantiated object either.

These types of methods aren't typically meant to create/instantiate objects, but they may contain some type of logic pertaining to the class itself, like a helper or utility method.

@classmethod vs @staticmethod

The most obvious thing between these decorators is their ability to create static methods within a class. These types of methods can be called on uninstantiated class objects, much like classes using the static keyword in Java.

There is really only one difference between these two method decorators, but it's a major one. You probably noticed in the sections above that @classmethod methods have a cls parameter sent to their methods, while @staticmethod methods do not.

This cls parameter is the class object we talked about, which allows @classmethod methods to easily instantiate the class, regardless of any inheritance going on. The lack of this cls parameter in @staticmethod methods make them true static methods in the traditional sense. They're main purpose is to contain logic pertaining to the class, but that logic should not have any need for specific class instance data.

A Longer Example

Now let's see another example where we use both types together in the same class:

# static.py

class ClassGrades:

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

    @classmethod
    def from_csv(cls, grade_csv_str):
        grades = map(int, grade_csv_str.split(', '))
        cls.validate(grades)
        return cls(grades)


    @staticmethod
    def validate(grades):
        for g in grades:
            if g < 0 or g > 100:
                raise Exception()

try:  
    # Try out some valid grades
    class_grades_valid = ClassGrades.from_csv('90, 80, 85, 94, 70')
    print 'Got grades:', class_grades_valid.grades

    # Should fail with invalid grades
    class_grades_invalid = ClassGrades.from_csv('92, -15, 99, 101, 77, 65, 100')
    print class_grades_invalid.grades
except:  
    print 'Invalid!'
$ python static.py
Got grades: [90, 80, 85, 94, 70]  
Invalid!  

Notice how the static methods can even work together with from_csv calling validate using the cls object. Running the code above should print out an array of valid grades, and then fail on the second attempt, thus printing out "Invalid!".

Conclusion

In this article you saw how both the @classmethod and @staticmethod decorators work in Python, some examples of each in action, and how they differ from each other. Hopefully now you can apply them to your own projects and use them to continue to improve the quality and organization of your own code.

Have you ever used these decorators before, and if so, how? Let us know in the comments!