What does “at” @ symbol do in Python

We use the “@” symbol at the start of a line for function or class decorators. A decorator is a function that takes another function as an argument, adds some functionalities, and returns the modified function. We can accomplish this without altering the source code for the original function. However, the modified function includes calls to the original function.

You can check:Use of decorators in Python

Decorators in Python

Decorating the functions allows us to easily add functionality to our existing functions. We can do this by adding that functionality inside the wrapper function. So, without modifying our original function in any way, we can just add or alter any code we want inside the wrapper function. For example, we have printed that the wrapper function executed this before the original function. This is true as the original function was executed after the wrapper message.

def decorator_func(original_func):
    def wrapper_func(*args, **kwargs):
        print('wrapper function executed this before {} function'.format(original_func.__name__))
        return original_func(*args, **kwargs)
    return wrapper_func

def full_name(first_name, last_name):
    print('full_name function ran with arguments ({}, {})'.format(first_name, last_name))
    print('And the full name is', first_name, last_name)
    
name = decorator_func(full_name)
name('Seepak', 'Kumar')

Output:

wrapper function executed this before full_name function
full_name function ran with arguments (Seepak, Kumar)
And the full name is Seepak Kumar

Use of “@” symbol in decorators in Python

We usually use the “@” symbol to decorate functions in python. But, to understand what exactly is happening we can decorate functions without using them. Essentially, the function we define after a decorator is passed as an argument to the function following the “@” symbol. We can decorate all the functions with the same decorator that needs the same line of code from the wrapper function. For adding different modifications to a single function, we can even chain multiple decorators on top of each other.

Even though, these two are functionally the same, decorators with “@” are somewhat easier to read especially when we chain multiple decorators together. So, the syntax with “@” is the same thing as the decorator function with the original function passed in as an argument. We can differentiate between the two from the code snippets below.

def decorator_func(original_func):
    def wrapper_func(*args, **kwargs):
        print('wrapper function executed this before {} function'.format(original_func.__name__))
        return original_func(*args, **kwargs)
    return wrapper_func

@decorator_func
def full_name(first_name, last_name):
    print('full_name function ran with arguments ({}, {})'.format(first_name, last_name))
    print('And the full name is', first_name, last_name)
    
full_name('Seepak', 'Kumar')

Output:

wrapper function executed this before full_name function
full_name function ran with arguments (Seepak, Kumar)
And the full name is Seepak Kumar

To execute the original function with the arguments, we can add *args and **kwargs to our wrapper function. We will also pass this into our original function. We do this to pass any number of positional or keyword arguments to our wrapper function.

One of the more common use cases of decorators in Python is logging. This helps in keeping track of how many times a specific function is run and what arguments were passed to that function. Another example of the use of decorators is for timing how long a function runs.

Use of “@” symbol in matrix multiplication in Python

An “@” symbol can also be used as a binary operator for matrix multiplication. Since used as a binary operator, here “@” is used in the middle of the line. The following code snippet illustrates this.

class Mat(list):
    def __matmul__(self, Y):
        X = self
        return Mat([[sum(X[i][k]*Y[k][j] for k in range(len(Y)))
                    for j in range(len(Y[0]))] for i in range(len(X))])

X = Mat([[2,5,4],
         [3,4,7]])
Y = Mat([[6,8],
         [4,2],
         [3,5]])

print(X @ Y)

Output:

[[44, 46], [55, 67]]

Read more: Matrix multiplication in Python using Pytorch

Leave a Reply

Your email address will not be published.