I\'m looking at some Python code which used the @
symbol, but I have no idea what it does. I also do not know what to search for as searching Python docs or Goo
In short, it is used in decorator syntax and for matrix multiplication.
In the context of decorators, this syntax:
@decorator
def decorated_function():
"""this function is decorated"""
is equivalent to this:
def decorated_function():
"""this function is decorated"""
decorated_function = decorator(decorated_function)
In the context of matrix multiplication, a @ b
invokes a.__matmul__(b)
- making this syntax:
a @ b
equivalent to
dot(a, b)
and
a @= b
equivalent to
a = dot(a, b)
where dot
is, for example, the numpy matrix multiplication function and a
and b
are matrices.
I also do not know what to search for as searching Python docs or Google does not return relevant results when the @ symbol is included.
If you want to have a rather complete view of what a particular piece of python syntax does, look directly at the grammar file. For the Python 3 branch:
~$ grep -C 1 "@" cpython/Grammar/Grammar
decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE
decorators: decorator+
--
testlist_star_expr: (test|star_expr) (',' (test|star_expr))* [',']
augassign: ('+=' | '-=' | '*=' | '@=' | '/=' | '%=' | '&=' | '|=' | '^=' |
'<<=' | '>>=' | '**=' | '//=')
--
arith_expr: term (('+'|'-') term)*
term: factor (('*'|'@'|'/'|'%'|'//') factor)*
factor: ('+'|'-'|'~') factor | power
We can see here that @
is used in three contexts:
A google search for "decorator python docs" gives as one of the top results, the "Compound Statements" section of the "Python Language Reference." Scrolling down to the section on function definitions, which we can find by searching for the word, "decorator", we see that... there's a lot to read. But the word, "decorator" is a link to the glossary, which tells us:
decorator
A function returning another function, usually applied as a function transformation using the
@wrapper
syntax. Common examples for decorators areclassmethod()
andstaticmethod()
.The decorator syntax is merely syntactic sugar, the following two function definitions are semantically equivalent:
def f(...): ... f = staticmethod(f) @staticmethod def f(...): ...
The same concept exists for classes, but is less commonly used there. See the documentation for function definitions and class definitions for more about decorators.
So, we see that
@foo
def bar():
pass
is semantically the same as:
def bar():
pass
bar = foo(bar)
They are not exactly the same because Python evaluates the foo expression (which could be a dotted lookup and a function call) before bar with the decorator (@
) syntax, but evaluates the foo expression after bar in the other case.
(If this difference makes a difference in the meaning of your code, you should reconsider what you're doing with your life, because that would be pathological.)
If we go back to the function definition syntax documentation, we see:
@f1(arg) @f2 def func(): pass
is roughly equivalent to
def func(): pass func = f1(arg)(f2(func))
This is a demonstration that we can call a function that's a decorator first, as well as stack decorators. Functions, in Python, are first class objects - which means you can pass a function as an argument to another function, and return functions. Decorators do both of these things.
If we stack decorators, the function, as defined, gets passed first to the decorator immediately above it, then the next, and so on.
That about sums up the usage for @
in the context of decorators.
@
In the lexical analysis section of the language reference, we have a section on operators, which includes @
, which makes it also an operator:
The following tokens are operators:
+ - * ** / // % @ << >> & | ^ ~ < > <= >= == !=
and in the next page, the Data Model, we have the section Emulating Numeric Types,
object.__add__(self, other) object.__sub__(self, other) object.__mul__(self, other) object.__matmul__(self, other) object.__truediv__(self, other) object.__floordiv__(self, other)
[...] These methods are called to implement the binary arithmetic operations (
+
,-
,*
,@
,/
,//
, [...]
And we see that __matmul__
corresponds to @
. If we search the documentation for "matmul" we get a link to What's new in Python 3.5 with "matmul" under a heading "PEP 465 - A dedicated infix operator for matrix multiplication".
it can be implemented by defining
__matmul__()
,__rmatmul__()
, and__imatmul__()
for regular, reflected, and in-place matrix multiplication.
(So now we learn that @=
is the in-place version). It further explains:
Matrix multiplication is a notably common operation in many fields of mathematics, science, engineering, and the addition of @ allows writing cleaner code:
S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r)
instead of:
S = dot((dot(H, beta) - r).T, dot(inv(dot(dot(H, V), H.T)), dot(H, beta) - r))
While this operator can be overloaded to do almost anything, in numpy
, for example, we would use this syntax to calculate the inner and outer product of arrays and matrices:
>>> from numpy import array, matrix
>>> array([[1,2,3]]).T @ array([[1,2,3]])
array([[1, 2, 3],
[2, 4, 6],
[3, 6, 9]])
>>> array([[1,2,3]]) @ array([[1,2,3]]).T
array([[14]])
>>> matrix([1,2,3]).T @ matrix([1,2,3])
matrix([[1, 2, 3],
[2, 4, 6],
[3, 6, 9]])
>>> matrix([1,2,3]) @ matrix([1,2,3]).T
matrix([[14]])
@=
While researching the prior usage, we learn that there is also the inplace matrix multiplication. If we attempt to use it, we may find it is not yet implemented for numpy:
>>> m = matrix([1,2,3])
>>> m @= m.T
Traceback (most recent call last):
File "", line 1, in
TypeError: In-place matrix multiplication is not (yet) supported. Use 'a = a @ b' instead of 'a @= b'.
When it is implemented, I would expect the result to look like this:
>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])