As you already know, decorators can help change the behavior of a function without modifying its code. In this topic, we will discuss three main built-in decorators that can help us to work with classes: staticmethod, classmethod, and property.
@staticmethod
The @staticmethod decorator can be used to bind a function to a class as its method. In the following example, we have the CharType class and the method to get the type of character:
class CharType(object):
@staticmethod
def get_type(char):
if char.isalpha():
return 'letter'
elif char.isdigit():
return 'digit'
else:
return 'other'
print(CharType.get_type('a')) # letter
print(CharType().get_type('1')) # digit
As you can see, we may access the static method without creating an instance of the class first. Of course, we can extend the functionality by adding other methods which will interact with the static methods within the class.
Note that such static methods do not have any mandatory parameters; in contrast to instance methods we have dealt with before, the functions decorated with the @staticmethod decorator do not take self as the first argument; nor do they accept the cls argument, as opposed to class methods you will read about below. Even though a static method belongs to a class and all its instances, it does not get any access to instance internals. This method is used for a matter of convenience or to make a better design for the code.
@classmethod
Now, let's move on to the @classmethod decorator. As you know, a "regular" method takes an instance of the class as its first argument and then we use self to refer to it. Class methods, on the contrary, do not require particular class instances; they have access only to general class attributes and properties. Because of this, their first parameter should always be cls, which represents the class itself, and not the class instance, denoted by self.
In the following example, we have the class User and a single string, containing both a name and a surname. Now, we need to process the string to receive the name and the surname as two separate string variables. We are going to do it with the help of the from_string method.
class User(object):
def __init__(self, name, surname):
self.name = name
self.surname = surname
def get_info(self):
return self.name + ' (' + self.surname + ')'
@classmethod
def from_string(cls, data):
name, surname = data.split(' ')
return cls(name, surname) # passing the string values to the initialization call
user = User.from_string('Santa Claus') # using the class name to call the method
print(user.get_info()) # Santa (Claus)
As you can see, class methods are used when we do not need attributes of specific instances but want to use the class information for some purpose. The most common example is alternative constructors. For one, if the required information comes from some outer source, a file, for example, in this case, we cannot pass the data directly as class instances. We may carry out some kind of preprocessing before actually creating a new object, you have seen this case in the example above. With the help of the decorator, we call a class method using the class itself rather than a class instance.
However, we can also call the from_string method on the User instance, even though it makes less sense.
user2 = user.from_string('Father Christmas')
user2.get_info() # Father (Christmas)@classmethod vs @staticmethod
To make it clearer, let's outline the main differences between static and class methods:
- Static methods have neither
clsnorselfas their parameters, so we cannot operate on the class or particular instances within the method. In general, they act similarly to functions outside the class and are often used as utility methods. - Class methods always take the class as the first argument, usually called
cls. They are frequently used as alternative constructors that create class objects for various use cases.
@property
Finally, let's create a basic example of the @property decorator usage. This one is designed to access a method as if it was an attribute of a class. Once again, we have an instance of the User class:
class User:
def __init__(self, name, surname):
self.name = name
self.surname = surname
self.full_name = self.name + ' ' + self.surname
user = User('Santa', 'Claus')
print(user.full_name) # Santa Claus
When we change the name of the user to 'Father', we notice that the full_name attribute of our object remains the same. It happens because we have only set it during the initialization.
user.name = 'Father'
print(user.name) # Father
print(user.full_name) # Santa Claus
It is a perfect case to use the @property decorator! If we define full_name using the decorator, we will evaluate the method every time it is accessed as an attribute.
class User:
def __init__(self, name, surname):
self.name = name
self.surname = surname
@property
def full_name(self):
return self.name + ' ' + self.surname
user = User('Santa', 'Claus')
print(user.full_name) # Santa Claus
user.name = 'Father'
print(user.name) # Father
print(user.full_name) # Father ClausSummary
In this topic, we have learned that:
- the
@staticmethoddecorator allows us to use a function without access to class instances as a class method; - the
@classmethoddecorator can be used to call a method of a class instead of a class instance; - the
@propertydecorator is helpful when we want to access a method as an attribute.
Now let's practice!