Have you ever wondered what @property means? Or what A @ B does? I’ll show you how the symbol @ is used in Python.
There are two use cases: decorators and matrix multiplication.
When to Use the @ Symbol in Python
The main use case of the symbol @ in Python are decorators. In Python, a decorator extends the functionality of an existing function or class.
For example, this piece of code . . .
def extend_behavior(func):} return func @extend_behavior def some_func(): pass
. . . does the exact same as this piece of code:
def extend_behavior(func): return func def some_func(): Pass some_func = extend_behavior(some_func)
Decorators: A Practical Example
Let’s say you have a function that divides two numbers:
def divide(x, y): return x / y
The problem with this function is that nothing prevents
y from being
You could solve this with an
if check. But for the sake of demonstration, let’s patch the issue using a decorator.
Let’s start by creating a decorator function called
def guard_zero(operate): def inner(x, y): if y == 0: print("Cannot divide by 0.") return return operate(x, y) return inner
This decorator . . .
operate()function as an argument.
- extends the function
operate()by creating an inner function with the extended behavior.
- returns the
inner()function—a new version of o
Now you can use the decorator
guard_zero() to extend the behavior of
divide() to ensure no divisions with
0 are made:
divide = guard_zero(divide)
This works, but there’s a more conventional approach to applying a decorator:
@guard_zero def divide(x, y): return x / y
This makes it more readable and the programmer’s intent becomes clear.
Now you can test the
divide() function with different inputs to see it do what it is supposed to:
print(divide(5, 0)) print(divide(5, 2))
Cannot divide by 0. None 2.5
None output originates from the fact that
None when the value
There are many decorators you’ll see, but here are the most common ones.
Common Decorators in Python
Let’s go through each with an example.
Tagging a method with
@property makes it possible to access an object’s method like a regular attribute:
weight.pounds() ---> weight.pounds
A property decorator makes the decorated method a getter method.
Say you have a
Mass class that stores the mass in kilos and pounds.
class Mass: def __init__(self, kilos): self.kilos = kilos self.pounds = kilos * 2.205
You can use it by:
mass = Mass(100) print(mass.kilos) print(mass.pounds)
Now, let’s modify the number of
kilos and see what happens to
mass.kilos = 1500 print(mass.pounds)
pounds didn’t change. This happened because we never updated
You can fix the issue by replacing the
pounds attribute with a
class Mass: def __init__(self, kilos): self.kilos = kilos def pounds(self): return self.kilos * 2.205
Now you may modify the number of
kilos and the
pounds will be correct:
mass = Mass(100) print(mass.pounds()) mass.kilos = 500 print(mass.pounds())
Now you can’t call
mass.pounds anymore because
pounds() is a method, not an attribute. If the
Mass class is used somewhere else, the above change breaks the code.
This is where a
@property decorator helps you.
If you mark the
pounds() method with
@property it allows you to call
mass.pounds without parentheses again.
class Mass: def __init__(self, kilos): self.kilos = kilos @property def pounds(self): return self.kilos * 2.205
pounds() method is now called a getter method. It doesn’t store the number of pounds in the object but computes it by converting kilos to pounds.
A class method is useful when you need a method that involves the class but isn’t instance-specific.
For example, you can create an alternative initializer method for a class by using a class method.
To create a class method in Python, decorate it with
Use Class Method as an Alternative Constructor
Let’s say you have a
Weight class that instantiates weight objects with kilos:
class Weight: def __init__(self, kilos): self.kilos = kilos
You can create
Weight objects like this:
w1 = Weight(100)
But if you want to create a
Weight object from pounds, you have to take care of converting pounds to kilos in advance.
pounds = 220.5 kilos = pounds / 2.205 w2 = Weight(kilos) print(w2.kilos)
So you always need to remember to convert pounds to kilos before creating a
Weight. This is bad practice.
Wouldn’t something like this be more efficient?
w2 = Weight.from_pounds(500)
To do this, let’s create a class method
from_pounds() that acts as an alternative constructor or a second initializer:
class Weight: def __init__(self, kilos): self.kilos = kilos @classmethod def from_pounds(cls, pounds): # convert pounds to kilos kilos = pounds / 2.205 # cls is the same as Weight. calling cls(kilos) is the same as Weight(kilos) return cls(kilos)
Let’s see how this class method works:
from_pounds()as a class method.
- The first argument
clsis a mandatory argument on a class method. It’s similar to self in a method. The difference is that
clsrepresents the whole
Weightclass, not just an instance of it.
- In the method, the
poundsare converted to
- The return
cls(kilos)is the same as
Simply put, this class method takes a number of
pounds as an argument, converts it to
kilos and returns a new
Now it’s possible to do:
w2 = Weight.from_pounds(220.5) print(w2.kilos)
A static method is tied to the class, not to its instance. This may remind you of a class method but the key difference is that a static method doesn’t modify the class at all. In other words, a static method doesn’t take
cls as its arguments.
We most often use a static method as a utility related to the class.
Let’s continue with the
Weight class. Start by adding a static method
conversion_info() to tell how kilos are converted to pounds:
class Weight: def __init__(self, kilos): self.kilos = kilos @classmethod def from_pounds(cls, pounds): # convert pounds to kilos kilos = pounds / 2.205 # cls is the same as Weight. calling cls(kilos) is the same as Weight(kilos) return cls(kilos) @staticmethod def conversion_info(): print("Kilos are converted to pounds by multiplying by 2.205.")
Now you can use this utility to print how the conversion happens:
Kilos are converted to pounds by multiplying by 2.205.
Since Python 3.5, it’s been possible to use
@ to multiply matrices.
For example, let’s create a matrix class, and implement the
__matmul__() method for matrix multiplication:
class Matrix(list): def __matmul__(self, B): A = self return Matrix([[sum(A[i][k] * B[k][j] for k in range(len(B))) for j in range(len(B)) ] for i in range(len(A))]) A = Matrix([[1, 2],[3, 4]]) B = Matrix([[5, 6],[7, 8]]) print(A @ B)
[[19, 22], [43, 50]]
There you have it: the
@ symbol in Python and how you can use it to clean up your code. Happy coding!