关于语法:在python中,”at”(@)符号做什么?

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

我在看一些使用@符号的python代码,但我不知道它会做什么。我也不知道要搜索什么,因为当包含@符号时,搜索python docs或google不会返回相关结果。


序言

我承认,我花了很长时间才完全理解这个概念,所以我将分享我所学到的,以拯救别人的麻烦。

名称修饰器——我们在函数定义之前使用@语法定义的东西——可能是这里的主要罪魁祸首。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Pizza(object):
    def __init__(self):
        self.toppings = []

    def __call__(self, topping):
        # When using '@instance_of_pizza' before a function definition
        # the function gets passed onto 'topping'.
        self.toppings.append(topping())

    def __repr__(self):
        return str(self.toppings)

pizza = Pizza()

@pizza
def cheese():
    return 'cheese'
@pizza
def sauce():
    return 'sauce'

print pizza
# ['cheese', 'sauce']

这表明,你定义的functionmethodclass,在@符号后,装饰器基本上就被作为argument传递给functionmethod

首次瞄准

微框架烧瓶从一开始就以以下格式介绍装饰师:

1
2
3
4
5
6
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return"Hello World!"

这反过来就意味着:

1
2
3
4
5
rule      ="/"
view_func = hello
# They go as arguments here in 'flask/app.py'
def add_url_rule(self, rule, endpoint=None, view_func=None, **options):
    pass

意识到这一点,我终于感觉到和烧瓶的平静。


行首的@符号用于类、函数和方法修饰符。

在这里阅读更多:

PEP 318:装饰师

Python装饰师

您遇到的最常见的python装饰器是:

@房产

@类方法

静力法

如果你在一行的中间看到一个@,那是另一回事,矩阵乘法。向下滚动查看使用@的其他答案。


此代码段:

1
2
3
4
5
6
def decorator(func):
   return func

@decorator
def some_func():
    pass

等同于此代码:

1
2
3
4
5
6
7
def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)

在decorator的定义中,可以添加一些通常函数不会返回的修改内容。


在python3.5中,可以将@作为操作符重载。它被命名为__matmul__,因为它是用来做矩阵乘法的,但是它可以是任何你想要的。详见PEP465。

这是矩阵乘法的一个简单实现。

1
2
3
4
5
6
7
8
9
10
class Mat(list):
    def __matmul__(self, B):
        A = self
        return Mat([[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 = Mat([[1,3],[7,5]])
B = Mat([[6,8],[4,2]])

print(A @ B)

此代码生成:

1
[[18, 14], [62, 66]]


"at"(@)符号在python中有什么作用?

简而言之,它用于修饰语法和矩阵乘法。

在decorator上下文中,此语法:

1
2
3
@decorator
def decorated_function():
   """this function is decorated"""

相当于:

1
2
3
4
def decorated_function():
   """this function is decorated"""

decorated_function = decorator(decorated_function)

在矩阵乘法的上下文中,a @ b调用a.__matmul__(b)—使此语法:

1
a @ b

相当于

1
dot(a, b)

1
a @= b

相当于

1
a = dot(a, b)

其中dot是numpy矩阵乘法函数,ab是矩阵。

你怎么能自己发现这个?

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.

如果您想对特定的Python语法有一个比较完整的了解,可以直接查看语法文件。对于python 3分支:

1
2
3
4
5
6
7
8
9
10
11
12
~$ 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

我们可以看到,@在三种情况下使用:

  • 装饰者
  • 因子之间的运算符
  • 增广的赋值运算符

修饰语法:

谷歌搜索"decorator python docs"的结果之一是"python language reference"的"compound statements"部分。向下滚动到函数定义部分,我们可以通过搜索"decorator"一词找到该部分,我们看到…有很多书要读。但是"decorator"这个词是词汇表的链接,它告诉我们:

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:

1
2
3
4
5
6
7
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.

所以,我们看到

1
2
3
@foo
def bar():
    pass

在语义上与以下内容相同:

1
2
3
4
def bar():
    pass

bar = foo(bar)

它们并不完全相同,因为python使用decorator(@语法在bar之前对foo表达式(可能是点式查找和函数调用)进行计算,而在另一种情况下,则是在bar之后对foo表达式进行计算。

(如果此差异对代码的含义造成影响,则应重新考虑您的生活在做什么,因为这将是病态的。)

堆积装饰工

如果我们回到函数定义语法文档,我们会看到:

1
2
3
@f1(arg)
@f2
def func(): pass

is roughly equivalent to

1
2
def func(): pass
func = f1(arg)(f2(func))

这是一个演示,我们可以先调用一个修饰符函数,然后调用堆栈修饰符。在Python中,函数是第一类对象——这意味着您可以将函数作为参数传递给另一个函数,并返回函数。装饰师做这两件事。

如果我们堆栈decorator,那么定义好的函数首先被传递给紧挨着它上面的decorator,然后传递给下一个,依此类推。

这大概总结了@在装饰师的背景下的用法。

接线员,@

在语言参考的词汇分析部分,我们有一个关于运算符的部分,其中包括@,这使得它也是一个运算符:

The following tokens are operators:

1
2
3
+       -       *       **      /       //      %      @
<<      >>      &amp;       |       ^       ~
<       >       <=      >=      ==      !=

在下一页的数据模型中,我们有一节,模拟数字类型,

1
2
3
4
5
6
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 (+, -, *, @, /, //, [...]

我们看到__matmul__对应于@。如果我们在文档中搜索"matmul",我们会得到一个指向python3.5中新功能的链接,其中"matmul"位于标题"pep465-用于矩阵乘法的专用中缀运算符"下。

it can be implemented by defining __matmul__(), __rmatmul__(), and
__imatmul__() for regular, reflected, and in-place matrix multiplication.

(现在我们了解到,@=是就地版本)。它进一步解释了:

Matrix multiplication is a notably common operation in many fields of
mathematics, science, engineering, and the addition of @ allows
writing cleaner code:

1
S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r)

instead of:

1
2
S = dot((dot(H, beta) - r).T,
        dot(inv(dot(dot(H, V), H.T)), dot(H, beta) - r))

虽然这个操作符可以被重载来做几乎任何事情,例如在numpy中,我们将使用这个语法来计算数组和矩阵的内外积:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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]])

在位矩阵乘法:@=

在研究先前的用法的同时,我们了解到还有就地矩阵乘法。如果我们尝试使用它,我们可能会发现它还没有为numpy实现:

1
2
3
4
5
>>> m = matrix([1,2,3])
>>> m @= m.T
Traceback (most recent call last):
  File"<stdin>", line 1, in <module>
TypeError: In-place matrix multiplication is not (yet) supported. Use 'a = a @ b' instead of 'a @= b'.

当它被实现时,我希望结果是这样的:

1
2
3
4
>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])


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

@符号是python提供使用decorator的语法。为了解释这个问题,这正是关于decorator在python中做什么的问题?

简单地说,decorator允许您修改给定函数的定义,而不需要触摸其最里面的部分(它的闭包)。最常见的情况是从第三方导入精彩的软件包。你可以想象它,你可以使用它,但是你不能触摸它的内心和心脏。

下面是一个简单的例子,假设我在ipython上定义了一个read_a_book函数

1
2
3
4
5
In [9]: def read_a_book():
   ...:     return"I am reading the book:"
   ...:
In [10]: read_a_book()
Out[10]: 'I am reading the book: '

你看,我忘了给它加个名字。如何解决这个问题?当然,我可以将函数重新定义为:

1
2
def read_a_book():
    return"I am reading the book: 'Python Cookbook'"

然而,如果不允许我操作原始函数,或者如果有数千个这样的函数需要处理,该怎么办呢?

用不同的思维来解决问题,并定义一个新的函数

1
2
3
4
def add_a_book(func):
    def wrapper():
        return func() +"Python Cookbook"
    return wrapper

然后使用它。

1
2
3
In [14]: read_a_book = add_a_book(read_a_book)
In [15]: read_a_book()
Out[15]: 'I am reading the book: Python Cookbook'

塔达,你看,我修改了read_a_book,没有触及它的内部封闭。没有什么能阻止我装备了decorator

关于@是什么?

1
2
3
4
5
@add_a_book
def read_a_book():
    return"I am reading the book:"
In [17]: read_a_book()
Out[17]: 'I am reading the book: Python Cookbook'

@add_a_book是一个很花哨和方便的方式来表达read_a_book = add_a_book(read_a_book),它是一种句法上的糖,没有什么比它更花哨的了。


如果您指的是使用numpy库的python笔记本中的一些代码,那么@ operator表示矩阵乘法。例如:

1
2
3
4
5
6
import numpy as np
def forward(xi, W1, b1, W2, b2):
    z1 = W1 @ xi + b1
    a1 = sigma(z1)
    z2 = W2 @ a1 + b2
    return z2, a1

从python 3.5开始,"@"用作矩阵乘法的专用中缀符号(pep 0465--请参阅https://www.python.org/dev/peps/pep-0465/)


@符号还用于访问plydata/pandas数据帧查询(pandas.DataFrame.query中的变量。例子:

1
2
3
4
df = pandas.DataFrame({'foo': [1,2,15,17]})
y = 10
df >> query('foo > @y') # plydata
df.query('foo > @y') # pandas

用另一种方式说别人有什么:是的,这是一个装饰师。

在python中,它就像:

  • 创建函数(在@call下)
  • 调用另一个函数对创建的函数进行操作。这将返回一个新函数。您调用的函数是@的参数。
  • 用返回的新函数替换定义的函数。
  • 这可以用于各种有用的事物,之所以成为可能,是因为函数是对象,只是必需的指令。


    它表示您正在使用一个装饰器。下面是BruceEckel 2008年的例子。