What does the “at” (@) symbol do in Python?

前端 未结 12 954
萌比男神i
萌比男神i 2020-11-22 01:57

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

12条回答
  •  醉话见心
    2020-11-22 02:38

    What does the “at” (@) symbol do in Python?

    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.

    How could you discover this on your own?

    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:

    • decorators
    • an operator between factors
    • an augmented assignment operator

    Decorator Syntax:

    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 are classmethod() and staticmethod().

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

    Stacked Decorators

    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.

    The Operator, @

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

    Inplace matrix multiplication: @=

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

提交回复
热议问题