How to Use the Magical @staticmethod, @classmethod, and @property Decorators in Python | by Lynn Kwong

Photo by Almos Bechtold on Unsplash

A decorator is a function that takes another function as input, extends its behavior, and returns a new function as output. This is possible because, in Python, functions are first-class objects, which means they can be passed as arguments to functions and also be returned from functions, just as other types of objects such as string, int, or float. A decorator can be used to decorate a function or a class.

In this article, three special decorators will be introduced, namely, @staticmethod, @classmethodand @property. These “magical” decorators can become very handy for our development work and make your code more clean.

In Python and other programming languages, a static method is a method that does not require the creation of an instance of a class. For Python, it means that the first argument of a static method is not self, but a regular positional or keyword argument. Also, a static method can have no arguments at all:

In this example, __init__ is a reserved method of Python and works as the constructor for the class. get_number is a regular instance method of the class and requires the creation of an instance. Especially get_emergency_number is a static method because it is decorated with the @staticmethod decorator. Also, it does not have self as the first argument, which means that it does not require the creation of an instance of the Cellphone class. Actually, get_emergency_number can just work as a standalone function. However, it makes sense and is intuitive to put it in the Cellphone class because a cellphone should be able to provide the emergency number.

This is a super simple example. Actually, in practice, if the Cellphone class has a country property, the get_emergency_number method would become an instance method because it needs to access the country property to provide the correct emergency number. However, this example should make it clear what is a static method.

In Python, a class method is created with the @classmethod decorator and requires the class itself as the first argument, which is written as cls. A class method normally works as a factory method and returns an instance of the class with supplied arguments. However, it doesn’t always have to work as a factory class and return an instance. You can create an instance in the class method, do whatever you need, and don’t have to return it:

In this example, iphone is a class method since it’s decorated with the @classmethod decorator and has cls as the first argument. It is a factory method here and returns an instance of the Cellphone class with the brand preset to “Apple”.

Class methods are very commonly used in third-party libraries, for example in the Scrapy web-scraping framework, it is used to customize a crawler:

It is more complex in the case of Scrapy, but the essence is the same. In your practical work, if you use class methods properly, you can reduce code redundancy and make your code more readable and more professional. The key point to keep in mind is that you can create an instance of the class based on some specific arguments in a class method. In this way, you don’t need to repeatedly create instances in other places of your code and thus make your code DRYer.

In the code snippet above, there is a function called get_number which returns the number of a Cellphone instance. We can optimize this method a bit and return a formatted phone number:

As we see, in this example, when we try to get the number of a cellphone, we don’t return it directly but do some formatting before returning it. This is a perfect case for using the @property decorator. In Python, with the @property decorator, you can use getter and setter to manage the attributes of your class instances very conveniently. The above code can be re-written with @propery like this:

However, if we run the code like above, we will encounter an AttributeError and can’t set the attribute. The reason is that we need to use a setter to set an attribute if we have used @property to get a property. Let’s create a setterthe syntax may look strange at first sight:

However, this time, we have a RecursionError error. This is because the number property is not a regular property anymore due to the decorations. Note that we have two number methods with the same name. self.number is now a property object with a getter and setterand not an instance property with a string value anymore.

To solve this problem, we need to use a different property name for the “real number” in the getter and setter. Let’s just introduce an underscore prefix to make it private property and avoid name conflicts. Actually, the whole point of use getter and setter is that the property of a class instance should not be obtained and changed directly, but done with some logic. In this example, in the getter, we format the number before it’s returned. And in the setter, we check if the number is a valid number before it’s set as a property. Let’s see if it works with this update:

Hooray, it works! Let’s try to set an invalid number to the cellphone and see what will happen:

cellphone.number = "123"
# ValueError: Invalid phone number.

Excellent, it works as expected. Invalid numbers will be rejected.

In case you are wondering why the syntax of the getter and setter is so weird, you can try to understand it by figuring out how decorator works. A decorator is a special function that returns a decorated function. In this example, under the hood, the built-in property class is used. property is a class even though it’s in lower case. Quite strange, isn’t it. But it’s not the strangest part. The first argument of the property class is a getter. The getter function will be triggered when you try to access the property object created, for example (cellphone.number).

Therefore, for the getter method, namely the first decorated number method in this example, we will get something like:

number = property(number)
  • The number in parentheses is the number method being decorated (the getter function).
  • The number on the left side is a property object returned by the property class.

A property object has a setter method that can set the setter method for the property object. Therefore, for the setter function, namely the second decorated number method, you will get something like:

number = number.setter(number) 
  • The number on the right side is the setter method number being decorated.
  • The number in the middle is the number (a property object) returned above, the one decorated with @propery.
  • The number on the left is the final property object with both the getter and setter assigned.

It is equivalent to the following form:

number = property(number).setter(number)

See if you can figure out what each number means.

The technical details of the @property decorator and the getter and setter are pretty complex and are related to the descriptor concept in Python. Don’t get frustrated, you don’t need to understand all the technical details of the @property decorator before using it. You can just use it as demonstrated in the Cellphone class. Very simple syntax to use.

In this article, three magical decorators in Python, namely @staticmethod, @classmethodand @propery are introduced with simple examples. You can now start to use it to optimize your code and make your code more professional.

The code is put together here for your reference:

Leave a Comment