What Is the @ Symbol in Python and How Do I Use It?

What does @property do? How about A @ B? In this tutorial I’ll walk you through all the different ways you can use the @ symbol in Python.

Written by Artturi Jalli
Published on Apr. 12, 2022
Brand Studio Logo

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 is decorators. In Python, a decorator is a function that extends the functionality of an existing function or class.

 

Decorators

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 0.

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 guard_zero():

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 . . .

  • accepts 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 operate() function.

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))

Output:

Cannot divide by 0.
None
2.5

(The None output originates from the fact that guard_zero() returns None when the value y is 0.)

Python Decorators in 15 Minutes

 

Common Decorators 

There are many decorators you’ll see, but here are the most common ones.

Common Decorators in Python

  • @property
  • @classmethod
  • @staticmethod

Let’s go through each with an example.

More Tutorials From Built In ExpertsStop Using NumPy’s Global Random Seed

 

@Property

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.

 

Example

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)

Result:

100
220.5

Now, let’s modify the number of kilos and see what happens to pounds:

mass.kilos = 1500
print(mass.pounds)

Output:

220.5

The pounds didn’t change. This happened because we never updated pounds.

You can fix the issue by replacing the pounds attribute with a pounds() method:

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())

Output:

220.5
1102.5

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

The 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.

Built In Tools for Built In Readers4 Tools to Speed Up Exploratory Data Analysis (EDA) in Python

 

@Classmethod

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 @classmethod.

 

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)

Output:

100

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:

  • The @classmethod marks the from_pounds() as a class method.
  • The first argument cls is a mandatory argument on a class method. It’s similar to self in a method. The difference is that cls represents the whole Weight class, not just an instance of it.
  • In the method, the pounds are converted to kilos.
  • The returncls(kilos) is the same as return Weight(kilos).

Simply put, this class method takes a number of pounds as an argument, converts it to kilos and returns a new Weight object.

Now it’s possible to do:

w2 = Weight.from_pounds(220.5)
print(w2.kilos)

Output:

100

More From Our Built In Engineering ExpertsCreate React App and TypeScript — A Quick How-To

 

@Staticmethod

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 self or 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:

Weight.conversion_info()

Result:

Kilos are converted to pounds by multiplying by 2.205.

Your Mom Doesn’t Work HereYour Variable Names Are a Mess. Clean Up Your Code.

 

Matrix Multiplication

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[0])) ] for i in range(len(A))])
A = Matrix([[1, 2],[3, 4]])
B = Matrix([[5, 6],[7, 8]])
print(A @ B)

Output:

[[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!

Explore Job Matches.