OOP in Python, part 4: Static methods

MP 41: Some methods don't need to work with attributes.

Note: This is the fourth post in a series about OOP in Python. The previous post discussed the importance of the __init__() method. The next post discusses class methods.


In this post we’ll look at static methods, which can do their work without access to any of the information stored in an instance’s attributes. Static methods are simpler than regular methods, because Python doesn’t need to provide them with all the information associated with an instance.

A method that doesn’t need self

You might think that every method needs a self argument. But that’s not always the case; if a method doesn’t need access to any attributes in order to do it’s work, it doesn’t need self.

For example, here’s a class called Mountain. It lets you store a little information about a mountain, and then display a description of the mountain:

class Mountain:

    def __init__(self, name, elev_meters=0):
        self.name = name
        self.elev_meters = elev_meters

    def describe_mountain(self):
        msg = f"{self.name} is {self.elev_meters:,} meters tall."
        print(msg)

my_mountain = Mountain("Mt. Verstovia", 1022)
my_mountain.describe_mountain()

We’ve created an instance of Mountain, and then called describe_mountain(). The output is a single sentence describing the mountain:

Mt. Verstovia is 1,022 meters tall.
Peak of Mt. Verstovia in Sitka, AK.
Mt Verstovia, the first mountain I climb when I haven’t been to the mountains in a while.

Now let’s say we want to let the user show a disclaimer about the dangers of climbing steep mountains. Here’s a first attempt, using self because we always include self in a method’s parameters:

class Mountain:

    def __init__(self, name, elev_meters=0):
        ...

    def describe_mountain(self):
        ...

    def show_disclaimer(self):
        msg = "\nClimbing steep mountains can be dangerous."
        msg += " If you are new to climbing,"
        msg += " please seek out instruction from"
        msg += " a qualified guide or an"
        msg += " experienced mentor."
        print(msg)

my_mountain = Mountain("Mt. Verstovia", 1022)
my_mountain.describe_mountain()

my_mountain.show_disclaimer()

The output shows a disclaimer after the mountain’s description:

Mt. Verstovia is 1,022 meters tall.

Climbing mountains can be dangerous. If you are new to climbing, please seek out instruction from a qualified guide or an experienced mentor.

The important thing to notice here is that the method show_disclaimer() does not use any specific information associated with self. The information is the same for every instance of Mountain that you could create.

Leaving out self

What happens if you simply leave self out of the method’s definition? It looks like we should be able to do this:

    def show_disclaimer():
        msg = "\nClimbing steep mountains can be dangerous."
        ...

It looks like we should be able to leave self out, because it’s not used anywhere in the method. But this version generates a traceback:

Traceback (most recent call last):
  File "mountains.py", line 22, in <module>
    verstovia.show_disclaimer()
TypeError: Mountain.show_disclaimer()
  takes 0 positional arguments but 1 was given

This tells us that the method show_disclaimer() in the class Mountain takes no arguments, but one is being passed to it. That argument is the self instance that Python passes to every method automatically.

Static methods

It’s really common to write some methods that don’t use any information associated with a specific instance. When that’s the case, there’s no need for Python to do the work of passing the method a reference to an instance.

Methods that don’t use any information associated with a specific instance are called static methods. The @staticmethod decorator tells Python not to pass the self argument to a method.1

Here’s show_disclaimer(), decorated with @staticmethod:

class Mountain:

    def __init__(self, name, elev_meters=0):
        ...

    def describe_mountain(self):
        ...

    @staticmethod
    def show_disclaimer():
        msg = "\nClimbing steep mountains can be dangerous."
        ...

verstovia = Mountain("Mt. Verstovia", 1022)
verstovia.describe_mountain()

verstovia.show_disclaimer()

Notice that show_disclaimer() now has no parameters, not even self.

This program has the same output as the one earlier, but Python isn’t doing the unnecessary work of passing all the information about the verstovia instance to show_disclaimer().

Because static methods don’t need information associated with any specific instance, you can call them without needing to create an instance. To call a static method without referencing a specific instance, use the name of the class and then the name of the method:

class Mountain:

    def __init__(self, name, elev_meters=0):
        ...

    def describe_mountain(self):
        ...

    @staticmethod
    def show_disclaimer():
        ...

Mountain.show_disclaimer()

This version of the program only displays the disclaimer:

Climbing steep mountains can be dangerous. If you are new to climbing, please seek out instruction from a qualified guide or an experienced mentor.

This gives you lots of flexibility in how the methods in a class are used.

The view from the peak of Mt. Verstovia.

Why use static methods?

You might be wondering if this really matters; what’s the harm in just including self in every method definition? There are two main benefits that are worth understanding:

Classes that use static methods where appropriate are clearer. When you decorate a method with @staticmethod, you’re not just telling Python how to handle the method. You’re also communicating to other developers that this method doesn’t modify any instance, and that they can call the method without first creating an instance.

Classes that use static methods are more efficient. Imagine a larger class with many attributes. Imagine that many instances have been created, and those instances are all calling a certain method many times. Adding the @staticmethod decorator eliminates a significant amount of work that Python would have been doing unnecessarily. This can have a noticeable effect in some projects.

One common use case for static methods is when refactoring longer methods into a set of smaller helper methods. Many helper methods don’t need information associated with an instance; they just get passed specific pieces of data, and transform that data in some way before returning a value. When you write a helper function that doesn’t access any data from the instance directly, make it a static method.

A real-world example

A great way to see how concepts like static methods are used in the real world is by searching for “staticmethod” in the repo of a library that you use. For example, I use the Plotly library for making dynamic charts and visualizations. Searching for “staticmethod” in the Plotly repository returns 11 code files.

Here’s one use of static methods in the Plotly codebase:

@staticmethod
def _display_frames_error():
    ...
    msg = """
    Frames are not supported by the plotly.graph_objs.FigureWidget class.
    Note: Frames are supported by the plotly.graph_objs.Figure class"""

    raise ValueError(msg)

This method defines an error message, msg, and then raises a ValueError with that message. The leading underscore in the method name indicates that this is an internal helper method, not one that end users are intended to use.

Conclusions

You can go a long way in OOP without using static methods, because including self as an argument in your method calls even when it’s not needed won’t cause any errors. But using static methods when appropriate will improve the clarity and efficiency of your code. Understanding what static methods are will also help you make sense of classes that use them.

In the next post we’ll look at one more special kind of method, class methods.

Resources

You can find the code files from this post in the mostly_python GitHub repository.


  1. A decorator is a special kind of function that runs before another function is called. For example, consider this pseudocode example:

    @my_decorator
    def my_function():
        ...

    Whenever my_function() is called, the function my_decorator() will be called first, and then the code in my_function() will be executed. A later post will cover decorators.