What Does the @ Symbol Do in Python?

When you write or review a Python function, an @ line above def can be hard to interpret at first. In Python, @ applies a decorator to the function being defined, while between operands it requests matrix multiplication.

I’ll show how Python applies decorators, then explain how operand types determine whether matrix multiplication works.

TL;DR: What @ Means in Python

Python uses @ in two distinct contexts: above a function definition it applies a decorator, while between two objects it requests matrix multiplication.

  • Before def: a decorator transforms the function object when the definition executes.
  • Between operands: @ calls the operand types’ matrix multiplication behavior.
  • The objects and their types determine which behavior applies.

What Does @ Mean in Python?

The symbol’s meaning comes from its position. In a function definition, @ introduces decorator syntax. In an expression such as A @ B, it is a binary operator.

A decorator is a callable that receives another callable and returns the object bound to the function’s name. The decorator syntax is a compact way to perform that reassignment. Matrix multiplication is separate: Python delegates @ to the types of its operands, so a library such as NumPy can define what the operation means for its arrays.

The same character therefore has two grammar roles, and neither role is selected by a setting or import. The parser can tell which role applies from the surrounding syntax. A decorator appears as a line in a function or class definition, while the operator sits between two expressions.

For a newcomer, a callable means an object that can be called with parentheses, such as a function. A decorator is itself callable because Python supplies the function object to it. The decorator then returns another object, often a wrapper function, that replaces the original name.

The two uses are related only by spelling. The decorator form belongs to definition syntax. The binary form belongs to expression evaluation.

I keep them in separate examples because combining both would hide how the parser chooses between them.

How to Read and Write @ in Python

The syntax tells you which operation is intended.

Step 1: Translate a decorator into an assignment

Python evaluates the decorator expression and passes the newly created function to it. The returned object is assigned to the function’s original name, which makes the compact form equivalent to an explicit assignment.

The order explains why a decorator can inspect or replace a function before any call to that function. Executing a module creates the function object, evaluates the decorator expression, and binds the decorator’s return value under the declared name.

That distinction is useful when a decorated function appears not to run its body. Applying the decorator builds or selects the replacement object. The wrapped function’s body ordinarily waits until later code calls the decorated name.

For a decorator factory, the expression has an extra call: a function first receives configuration and returns the actual decorator. In syntax such as @retry(3), retry(3) is evaluated during definition, and the callable it returns receives the function object.

@announce
def greet(name):
    print(f"Hello, {name}")

This is equivalent in effect to defining the function and then assigning the result of announce(greet) back to greet. It is not a call to greet: the function body runs later when the resulting greet is called.

Step 2: Follow the wrapper when the function is called

A wrapper can add behavior before and after calling the original function. I ran the complete example from decorator_demo.py with python3 decorator_demo.py; the terminal returned the lines shown below.

before
Hello, Ada
after
greet
Terminal output from a Python decorator example
A wrapper runs around the decorated function call.

The before and after lines come from the wrapper, and Hello, Ada comes from the original function. The final greet line confirms that functools.wraps preserves the wrapped function’s name in this example. See the decorator examples in Python for more decorator forms.

Step 3: Read stacked decorators from bottom to top

With multiple decorators, Python applies the one nearest the def line first and then wraps that result with the decorator above it. For @first above @second, the binding is first(second(function)).

This is function composition: the lower decorator receives the original function, and the upper decorator receives the lower decorator’s result. Reversing the lines changes the composition, which can change behavior when one decorator expects a particular callable or descriptor.

Built-in decorators use the same syntax but return objects with different roles. @property turns a method into an attribute-style descriptor, while @classmethod and @staticmethod adjust how a method is bound when accessed through a class or instance.

For example, a property is read as object.value rather than object.value(). This can make computed data fit an attribute interface, but it also means the decorated method is no longer called as an ordinary method at the call site. The Python property decorator guide covers that specific use.

@first
@second
def task():
    ...

This order matters when decorators change arguments, return values or metadata. Use functools.wraps inside a wrapper when the wrapper should retain the original function’s name and documentation.

Step 4: Use @ between matrix operands

Between operands, @ requests matrix multiplication. The operator was added in Python 3.5, but core Python objects such as ordinary lists do not gain matrix multiplication from the syntax alone.

Matrix multiplication combines rows from a left-hand matrix with columns from a right-hand matrix. For each output cell, corresponding values are multiplied and those products are summed. This differs from element-wise multiplication, which combines values at matching positions without combining rows and columns.

For two-dimensional matrices, the number of columns on the left must match the number of rows on the right. If the left shape is m by n and the right shape is n by p, the product has shape m by p. The inner dimension n is the compatibility check.

Python’s syntax does not choose a numeric storage format or install a matrix library. NumPy arrays implement @, but a plain nested list does not. I would use NumPy when working with numerical arrays because its array type defines both matrix multiplication and the associated dimensional rules.

# With NumPy arrays:
# result = left @ right

For NumPy arrays, this is matrix multiplication, while * is element-wise multiplication. A custom class can define the operator through __matmul__. That method’s implementation determines whether its operation is a conventional matrix product.

See AskPython’s introduction to linear algebra for the matrix concepts behind the operation.

When Does @ Work, and What Can Go Wrong?

A decorator requires a valid decorator expression, and binary @ requires operand types that implement matrix multiplication. Checking the syntax position and operand types is the quickest way to diagnose confusion.

Code contextMeaningWhat to check
@decorate above defTransform function at definition timeDecorator returns the intended callable or descriptor
left @ rightMatrix multiplication dispatchOperand types support __matmul__
left * rightType-defined multiplication, often element-wise for arraysConsult the library’s type behavior

A TypeError for A @ B commonly means the operand types do not support that operation. Convert values to a matrix-aware type or use the operation the type documents. Adding @ cannot supply a missing implementation.

Arrays can both support @ and still be incompatible because their inner dimensions differ. Check each array’s shape before multiplication, then confirm that the left column count equals the right row count.

Custom classes can implement __matmul__ to define the left operand’s behavior. Python may also try the right operand’s reflected method, __rmatmul__, when appropriate. These hooks let libraries support their own array or symbolic types, but they do not force every class to treat @ as ordinary numeric matrix multiplication.

A decorated function may return a value that is not callable. That can be valid when the decorator is used for a descriptor or registration task, but later code cannot invoke that name as a function. When a call raises TypeError after adding a decorator, inspect what the decorator returns and whether it preserves the intended interface.

Arguments need attention too. A wrapper that accepts no parameters only works for a wrapped function that takes no arguments. A general-purpose wrapper forwards positional and keyword arguments, then returns the wrapped function’s result so callers receive the same value contract.

The standard functools.wraps helper addresses a different concern. It copies metadata such as the wrapped function’s name and documentation onto the wrapper. It does not forward arguments by itself and does not change what the wrapped function computes.

The symbol does not mean numeric sign in either context. Questions about changing a number’s sign or checking whether it is positive belong to arithmetic, not Python’s @ syntax.

When the source is unfamiliar, inspect one line above and the tokens on either side of @. That check separates definition-time decoration from an operator expression before debugging the implementation.

Conclusion: Read @ From Its Position

Above def, @ applies a decorator to the function being defined. Between operands, it dispatches matrix multiplication to the objects’ types, so the operands determine whether the expression works.

For decorator details, consult the Python function definition reference and the linked AskPython decorator and linear algebra guides.

FAQ

These answers distinguish decorator syntax from the matrix operator.

What does @ do before a function in Python?

It applies a decorator to the function when its definition executes. The decorator receives the function and the returned object is bound to its name.

Can I use @ for matrix multiplication in Python?

Yes, when the operand types implement matrix multiplication. Python dispatches the operator to their matrix multiplication behavior.

Does @ mean the same thing everywhere in Python?

No. Above a function definition it introduces decorator syntax; between operands it is the matrix multiplication operator.

Tanishka Dhondge
Tanishka Dhondge
Articles: 59