在Python中,“at”(@)符号有什么作用?

940
在Python中,@符号有什么作用?
14个回答

565

105
看起来它也可以作为矩阵乘法运算符:https://dev59.com/KGEi5IYBdhLWcg3wK5nd#21563036 - Pro Q

444

例子

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

这表明在装饰器之后定义的函数/方法/只是被作为@符号后面的函数/方法参数立即传递。

第一次出现

微框架Flask从一开始就使用以下格式引入装饰器

from flask import Flask
app = Flask(__name__)

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

这反过来又转化为:

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

最终意识到这一点让我对 Flask 感到平静。


14
在 Flask 的 app.route("/") 案例中,该函数返回一个函数,您可以使用您的 hello() 作为参数来调用它。 - shaqed
8
在这里使用装饰器的句法或实际好处是什么,与直接在定义“hello”之后立即调用类似于app.route("/", hello)的方式相比,或者甚至在app.route参数中将“hello”定义为lambda函数更好吗?(后者是Node.js http.Server和Express路由常见的例子。) - iono
3
我猜看起来很酷。 - chai

296
在Python 3.5中,您可以将@作为操作符进行重载。它被命名为__matmul__,因为它的设计初衷是执行矩阵乘法,但实际上可以用于任何你想要的操作。详情请参见PEP465
这是一个简单的矩阵乘法实现。
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)

这段代码产生的结果是:

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

35
你还有@=(原地修改)运算符,它的名称是__imatmul__ - Pål GD
4
还有哪些可以重载的运算符?我知道__add____sub__分别与加号和减号关联,但从未听说过与@符号相关的运算符。还有其他隐藏的运算符吗? - Thomas Kimber
4
@ThomasKimber 当然。请查看https://docs.python.org/3/reference/datamodel.html?highlight=matmul#emulating-numeric-types下的所有内容。 - RandyP

269

这段代码示例:

def decorator(func):
   return func

@decorator
def some_func():
    pass

等同于以下代码:

def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)
在装饰器的定义中,您可以添加一些修改过的内容,这些内容通常不会由函数返回。

5
在这行代码中,"some_func = decorator(some_func)",第一个 some_func 是一个变量,等于函数 some_func,是这样吗? - Viragos
1
@Viragos 您正在将名称 some_func 定义为由 decorator(some_func) 给出的函数。因此,两个 some_func 实例都是函数,第一个实例只是被保存为装饰版本。 - Eli Harold

182

Python中的“at” (@)符号有什么作用?

简单来说,它用于装饰器语法和矩阵乘法。

在装饰器的上下文中,使用以下语法:

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

相当于这个:

def decorated_function():
    """this function is decorated"""

decorated_function = decorator(decorated_function)

在矩阵乘法的上下文中,a @ b 调用 a.__matmul__(b) - 使这个语法成为:

a @ b

等同于

dot(a, b)

a @= b

等同于

a = dot(a, b)

这里的dot是numpy中的矩阵乘法函数,ab是矩阵。

你如何自己发现这个内容?

我也不知道该搜索什么,因为在搜索Python文档或谷歌时,包含@符号的结果并不相关。

如果您想全面了解特定Python语法的工作原理,请直接查看语法文件。对于Python 3版本分支:

~$ 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:

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.

因此,我们可以看到

@foo
def bar():
    pass

在语义上与以下代码相同:

def bar():
    pass

bar = foo(bar)

他们并不完全相同,因为Python在装饰器(@)语法中会先评估foo表达式(可能是点查找和函数调用),然后再评估bar,但在另一种情况下,则在bar之后评估foo表达式。

(如果这种差异对您代码的含义有影响,您应该重新考虑自己的生活,因为那将是病态的。)

堆叠装饰器

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

@f1(arg)
@f2
def func(): pass

is roughly equivalent to

def func(): pass
func = f1(arg)(f2(func))
这是一个演示,我们可以先调用一个作为装饰器的函数,然后堆叠多个装饰器。在Python中,函数是一等对象,这意味着您可以将一个函数作为参数传递给另一个函数,并返回函数。装饰器同时执行这两个操作。
如果我们堆叠装饰器,定义的函数首先会被立即上面的装饰器接收,然后是下一个,以此类推。
这大概总结了在装饰器上下文中使用@的用法。
运算符@ 在语言参考的词法分析部分,我们有一个关于运算符的部分,其中包括@,这也使其成为一个运算符:

The following tokens are operators:

+       -       *       **      /       //      %      @
<<      >>      &       |       ^       ~
<       >       <=      >=      ==      !=
在下一页——数据模型中,我们有模拟数字类型部分。
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"的文档,我们会得到一个链接,指向Python 3.5中的新变化,其中"matmul"在标题为"PEP 465-专用中缀运算符矩阵乘法"下。它可以通过定义__matmul__()__rmatmul__()__imatmul__()来实现常规、反射和原地矩阵乘法。(因此,我们现在知道@=是原地版本)。它进一步解释道:

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

虽然这个运算符可以被重载以执行几乎任何操作,但在numpy中,我们会使用以下语法来计算数组和矩阵的内积和外积:

>>> 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实现:

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

当它被实现时,我希望结果看起来像这样:
>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])

精彩的概述。我喜欢你是如何自己发现这个问题的?部分! - nealmcb

93

Python 中的“@”符号有什么作用?

在 Python 中,“@”符号是一种语法糖,用于利用装饰器(decorator)。
换句话说,它正是关于 Python 中装饰器的作用是什么。

简单来说,decorator 允许您修改给定函数的定义而不影响其内部(即闭包)的内容。
这通常发生在您从第三方导入优秀的软件包时。您可以看到它,使用它,但不能触及其内部和核心。

以下是一个快速示例,
假设我在 IPython 上定义了一个名为 read_a_book 的函数:

In [9]: def read_a_book():
   ...:     return "I am reading the book: "
   ...: 
In [10]: read_a_book()
Out[10]: 'I am reading the book: '
你看,我忘记给它加上一个名称了。
如何解决这个问题?当然,我可以重新定义函数为:
def read_a_book():
    return "I am reading the book: 'Python Cookbook'"

尽管如此,如果我不被允许操作原始函数,或者有成千上万个这样的函数需要处理,该怎么办呢?

通过不同的思考方式来解决问题,并定义一个新函数。

def add_a_book(func):
    def wrapper():
        return func() + "Python Cookbook"
    return wrapper

那就利用它。

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函数的功能,而没有修改它内部的闭包。有了装饰器,我可以做任何事情。

那么@符号是什么意思呢?

@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),它是语法糖,没有更多的花哨之处。


8
这是整个页面中最好的一页,你解释得非常好,只有在读了你的回答后我才能理解!太棒了! - MMEL

25

如果您在Python笔记本中引用使用Numpy库的一些代码,则@运算符表示矩阵乘法。例如:

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

15

在Python中添加了装饰器,以使得函数和方法包装(一个接收函数并返回增强版的函数)更易于阅读和理解。最初的用例是能够将方法定义为类方法或静态方法。如果没有装饰器语法,则需要编写相当稀疏和重复的代码来定义:

class WithoutDecorators:
def some_static_method():
    print("this is static method")
some_static_method = staticmethod(some_static_method)

def some_class_method(cls):
    print("this is class method")
some_class_method = classmethod(some_class_method)

如果使用装饰器语法来实现同样的目的,代码会更短,也更易于理解:

class WithDecorators:
    @staticmethod
    def some_static_method():
        print("this is static method")

    @classmethod
    def some_class_method(cls):
        print("this is class method")

常规语法和可能的实现

装饰器通常是一个具有名称的对象(不允许使用lambda表达式),在调用时接受单个参数(它将是被装饰的函数),并返回另一个可调用对象。这里使用“可调用对象”而不是“函数”是有预谋的。虽然装饰器通常在方法和函数的范围内讨论,但它们不仅局限于此。实际上,任何可调用的东西(任何实现了_call__方法的对象都被视为可调用),都可以用作装饰器,并且它们返回的对象通常不是简单的函数,而是更复杂的类的实例,实现了自己的__call__方法。

装饰器语法只是一种语法糖。考虑以下装饰器用法:

@some_decorator
def decorated_function():
    pass

这总是可以通过显式的装饰器调用和函数重新赋值来替换:

def decorated_function():
    pass
decorated_function = some_decorator(decorated_function)

然而,如果在单个函数上使用多个装饰器,则后者不太容易阅读并且很难理解。 装饰器可以用多种不同的方式使用,如下所示:

作为函数

有许多编写自定义装饰器的方法,但最简单的方法是编写一个返回包装原始函数调用的子函数的函数。

常见的模式如下:

def mydecorator(function):
    def wrapped(*args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result
    # return wrapper as a decorated function
    return wrapped

作为一个类

虽然装饰器几乎总是可以使用函数实现,但在某些情况下,使用用户定义的类可能是更好的选择。通常情况下,当装饰器需要复杂的参数化或者依赖于特定状态时,这种情况就是使用类作为装饰器的最佳选择。

非参数化装饰器的通用模式作为一个类如下:

class DecoratorAsClass:
    def __init__(self, function):
        self.function = function

    def __call__(self, *args, **kwargs):
        # do some stuff before the original
        # function gets called
        result = self.function(*args, **kwargs)
        # do some stuff after function call and
        # return the result
        return result

装饰器的参数化

在实际代码中,通常需要使用可以带参数的装饰器。当函数用作装饰器时,解决方案很简单——需要使用第二层包装。这里是一个简单的例子,展示了一个装饰器,它会在每次调用被装饰的函数时重复执行指定次数:

def repeat(number=3):
"""Cause decorated function to be repeated a number of times.

Last value of original function call is returned as a result
:param number: number of repetitions, 3 if not specified
"""
def actual_decorator(function):
    def wrapper(*args, **kwargs):
        result = None
        for _ in range(number):
            result = function(*args, **kwargs)
        return result
    return wrapper
return actual_decorator

这样定义的装饰器可以接受参数:

>>> @repeat(2)
... def foo():
...     print("foo")
...
>>> foo()
foo
foo

请注意,即使参数化的装饰器有默认值,其名称后面的括号也是必需的。正确使用带有默认参数的前面装饰器的方法如下:

>>> @repeat()
... def bar():
...     print("bar")
...
>>> bar()
bar
bar
bar

最后,让我们看看如何使用属性修饰符。

属性

属性提供了一个内置的描述符类型,可以将属性与一组方法链接起来。属性接受四个可选参数:fget,fset,fdel和doc。最后一个参数可以用来定义文档字符串,就像它是一个方法一样与属性相关联。下面是一个矩形类的示例,可以通过直接访问存储两个角点的属性或使用宽度、高度属性来控制:

class Rectangle:
    def __init__(self, x1, y1, x2, y2):
        self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2

    def _width_get(self):
        return self.x2 - self.x1

    def _width_set(self, value):
        self.x2 = self.x1 + value

    def _height_get(self):
        return self.y2 - self.y1

    def _height_set(self, value):
        self.y2 = self.y1 + value

    width = property(
        _width_get, _width_set,
        doc="rectangle width measured from left"
    )
    height = property(
        _height_get, _height_set,
        doc="rectangle height measured from top"
    )

    def __repr__(self):
        return "{}({}, {}, {}, {})".format(
            self.__class__.__name__,
            self.x1, self.y1, self.x2, self.y2
    )

创建属性的最佳语法是使用装饰器作为属性。这样可以减少类中方法签名的数量,使代码更加易读且易于维护。使用装饰器后,上述类变成了:

class Rectangle:
    def __init__(self, x1, y1, x2, y2):
        self.x1, self.y1 = x1, y1
        self.x2, self.y2 = x2, y2

    @property
    def width(self):
        """rectangle height measured from top"""
        return self.x2 - self.x1

    @width.setter
    def width(self, value):
        self.x2 = self.x1 + value

    @property
    def height(self):
        """rectangle height measured from top"""
        return self.y2 - self.y1

    @height.setter
    def height(self, value):
        self.y2 = self.y1 + value

12

7

@ 可以是数学运算符或装饰器,但你想要的是装饰器。

这段代码:

def func(f):
    return f

func(lambda :"HelloWorld")()

使用装饰器可以这样编写:

def func(f):
    return f
@func
def name():
    return "Hello World"

name()

装饰器可以带有参数。

你可以查看这篇GeeksforGeeks的文章:https://www.geeksforgeeks.org/decorators-in-python/


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接