Biểu tượng trên mạng tại điểm (@) làm gì trong Python?


580

Tôi đang xem một số mã Python sử dụng @ký hiệu này, nhưng tôi không biết nó làm gì. Tôi cũng không biết tìm kiếm gì khi tìm kiếm tài liệu Python hoặc Google không trả về kết quả có liên quan khi @bao gồm biểu tượng.

Câu trả lời:


304

Một @biểu tượng ở đầu một dòng được sử dụng cho các trình trang trí lớp, chức năng và phương thức .

Đọc thêm tại đây:

PEP 318: Trang trí

Trang trí Python

Các trình trang trí Python phổ biến nhất bạn sẽ gặp phải là:

@bất động sản

@ classmethod

@staticmethod

Nếu bạn thấy một @dòng ở giữa một dòng, đó là một điều khác, nhân ma trận. Cuộn xuống để xem các câu trả lời khác sử dụng @.


31
Có vẻ như nó cũng có thể là một toán tử nhân ma trận: stackoverflow.com/a/21563036/5049813
Pro Q

@decorators cũng có thể được thêm vào
Vijay Panchal

348

Thí dụ

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

Điều này cho thấy rằng function/ method/ classbạn đang xác định sau khi trang trí về cơ bản chỉ được chuyển argumentthành function/ methodngay sau @dấu hiệu.

Nhìn thấy đầu tiên

Flask microframework Flask giới thiệu các trang trí ngay từ đầu ở định dạng sau:

from flask import Flask
app = Flask(__name__)

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

Điều này lần lượt dịch sang:

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

Nhận ra điều này cuối cùng đã cho phép tôi cảm thấy bình yên với Flask.


7
Trong trường hợp app.route("/")hello()
Fl Nhiệm

3
Lợi ích cú pháp hoặc thực tế của việc có các nhà trang trí ở đây là gì, thay vì (ví dụ) chỉ gọi một cái gì đó như app.route("/", hello)ngay lập tức sau khi xác định hello, hoặc thậm chí định nghĩa hellolà lambda trong các đối số app.route? (Ví dụ sau là phổ biến với các tuyến Node.js http.Servervà Express.)
iono

185

Đoạn mã này:

def decorator(func):
   return func

@decorator
def some_func():
    pass

Tương đương với mã này:

def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)

Trong định nghĩa của trình trang trí, bạn có thể thêm một số thứ đã được sửa đổi mà hàm sẽ không được trả về một cách bình thường.


1
Trong dòng này "ome_func = decorator (some_func)", some_func đầu tiên là một biến = cho hàm some_func, đúng không?
Viragos

147

Trong Python 3.5, bạn có thể quá tải @như một toán tử. Nó được đặt tên là __matmul__, bởi vì nó được thiết kế để thực hiện phép nhân ma trận, nhưng nó có thể là bất cứ điều gì bạn muốn. Xem PEP465 để biết chi tiết.

Đây là một thực hiện đơn giản của phép nhân ma trận.

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)

Mã này mang lại:

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

14
Bạn cũng có toán tử @=(tại chỗ), đó là __imatmul__.
Pål GD

Có bất kỳ nhà khai thác quá mức khác như thế này? Tôi biết __add____sub__được liên kết với + và - tương ứng, nhưng chưa bao giờ nghe thấy @dấu hiệu nào trước đó. Có ai khác đang rình rập ngoài kia không?
Thomas Kimber

103

Biểu tượng trên mạng tại điểm (@) làm gì trong Python?

Nói tóm lại, nó được sử dụng trong cú pháp trang trí và nhân ma trận.

Trong ngữ cảnh của trang trí, cú pháp này:

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

tương đương với điều này:

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

decorated_function = decorator(decorated_function)

Trong ngữ cảnh nhân ma trận, a @ bgọi a.__matmul__(b)- thực hiện cú pháp này:

a @ b

tương đương với

dot(a, b)

a @= b

tương đương với

a = dot(a, b)

trong đó dot, ví dụ, hàm nhân ma trận numpy ablà ma trận.

Làm thế nào bạn có thể tự mình khám phá điều này?

Tôi cũng không biết tìm kiếm gì khi tìm kiếm tài liệu Python hoặc Google không trả về kết quả có liên quan khi bao gồm biểu tượng @.

Nếu bạn muốn có một cái nhìn khá đầy đủ về những gì một cú pháp python cụ thể làm, hãy nhìn thẳng vào tệp ngữ pháp. Đối với nhánh 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

Chúng ta có thể thấy ở đây @được sử dụng trong ba bối cảnh:

  • trang trí
  • một toán tử giữa các yếu tố
  • một toán tử gán tăng

Cú pháp trang trí:

Một tìm kiếm google cho "tài liệu python trang trí" đưa ra như một trong những kết quả hàng đầu, phần "Tuyên bố hợp chất" của "Tham chiếu ngôn ngữ Python". Cuộn xuống phần định nghĩa hàm , mà chúng ta có thể tìm thấy bằng cách tìm kiếm từ "trang trí", chúng ta thấy rằng ... có rất nhiều thứ để đọc. Nhưng từ "trang trí" là một liên kết đến bảng chú giải , cho chúng ta biết:

người trang trí

Một hàm trả về một hàm khác, thường được áp dụng như một phép biến đổi hàm bằng @wrappercú pháp. Ví dụ phổ biến cho trang trí là classmethod()staticmethod().

Cú pháp trang trí chỉ là cú pháp cú pháp, hai định nghĩa hàm sau tương đương về mặt ngữ nghĩa:

def f(...):
    ...
f = staticmethod(f)

@staticmethod
def f(...):
    ...

Khái niệm tương tự tồn tại cho các lớp, nhưng ít được sử dụng ở đó. Xem tài liệu về định nghĩa hàm và định nghĩa lớp để biết thêm về trang trí.

Vì vậy, chúng tôi thấy rằng

@foo
def bar():
    pass

về mặt ngữ nghĩa giống như:

def bar():
    pass

bar = foo(bar)

Chúng không hoàn toàn giống nhau vì Python đánh giá biểu thức foo (có thể là tra cứu chấm và gọi hàm) trước thanh với @cú pháp trang trí ( ), nhưng đánh giá biểu thức foo sau thanh trong trường hợp khác.

(Nếu sự khác biệt này tạo ra sự khác biệt về ý nghĩa của mã của bạn, bạn nên xem xét lại những gì bạn đang làm với cuộc sống của mình, vì đó sẽ là bệnh lý.)

Trang trí xếp chồng

Nếu chúng ta quay lại tài liệu cú pháp định nghĩa hàm, chúng ta sẽ thấy:

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

gần tương đương với

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

Đây là một minh chứng rằng chúng ta có thể gọi một chức năng là trang trí đầu tiên, cũng như trang trí ngăn xếp. Các hàm, trong Python, là các đối tượng lớp đầu tiên - có nghĩa là bạn có thể truyền một hàm làm đối số cho một hàm khác và trả về các hàm. Trang trí làm cả hai điều này.

Nếu chúng ta xếp chồng trang trí, chức năng, như được xác định, trước tiên được chuyển đến công cụ trang trí ngay trên nó, sau đó tiếp theo, v.v.

Đó là về tổng hợp việc sử dụng @trong bối cảnh trang trí.

Người vận hành, @

Trong phần phân tích từ vựng của tài liệu tham khảo ngôn ngữ, chúng tôi có một phần về các toán tử , bao gồm @, nó cũng là một toán tử:

Các mã thông báo sau là toán tử:

+       -       *       **      /       //      %      @
<<      >>      &       |       ^       ~
<       >       <=      >=      ==      !=

và trong trang tiếp theo, Mô hình dữ liệu, chúng ta có phần Mô phỏng các kiểu số ,

object.__add__(self, other)
object.__sub__(self, other) 
object.__mul__(self, other) 
object.__matmul__(self, other) 
object.__truediv__(self, other) 
object.__floordiv__(self, other)

[...] Những phương pháp này được gọi là để thực hiện các phép tính số học nhị phân ( +, -, *, @, /, //, [...]

Và chúng tôi thấy rằng __matmul__tương ứng với @. Nếu chúng tôi tìm kiếm tài liệu cho "matmul", chúng tôi sẽ nhận được một liên kết đến Có gì mới trong Python 3.5 với "matmul" trong tiêu đề "PEP 465 - Một toán tử infix chuyên dụng để nhân ma trận".

nó có thể được thực hiện bằng cách định nghĩa __matmul__(), __rmatmul__()__imatmul__()cho thường xuyên, phản ánh, và tại chỗ nhân ma trận.

(Vì vậy, bây giờ chúng tôi tìm hiểu đó @=là phiên bản tại chỗ). Nó giải thích thêm:

Phép nhân ma trận là một hoạt động phổ biến đáng chú ý trong nhiều lĩnh vực toán học, khoa học, kỹ thuật và việc thêm @ cho phép viết mã sạch hơn:

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

thay vì:

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

Mặc dù toán tử này có thể bị quá tải để làm hầu hết mọi thứ, numpyví dụ, chúng tôi sẽ sử dụng cú pháp này để tính toán sản phẩm bên trong và bên ngoài của mảng và ma trận:

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

Phép nhân ma trận tại chỗ: @=

Trong khi nghiên cứu việc sử dụng trước đó, chúng tôi biết rằng đó cũng là phép nhân ma trận tại chỗ. Nếu chúng tôi cố gắng sử dụng nó, chúng tôi có thể thấy nó chưa được triển khai cho 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'.

Khi nó được thực hiện, tôi mong đợi kết quả sẽ như thế này:

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

36

Biểu tượng trên mạng tại điểm (@) làm gì trong Python?

Biểu tượng @ là một con trăn đường cú pháp cung cấp để sử dụng decorator,
để diễn giải câu hỏi, Đó chính xác là những gì trang trí làm trong Python?

Nói một cách đơn giản, decoratorcho phép bạn sửa đổi định nghĩa của một chức năng nhất định mà không cần chạm vào bên trong của nó (nó đóng).
Đó là trường hợp nhất khi bạn nhập gói tuyệt vời từ bên thứ ba. Bạn có thể hình dung nó, bạn có thể sử dụng nó, nhưng bạn không thể chạm vào bên trong và trái tim của nó.

Dưới đây là một ví dụ nhanh,
giả sử tôi xác định một read_a_bookhàm trên Ipython

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

Bạn thấy đấy, tôi quên thêm một cái tên cho nó.
Làm thế nào để giải quyết vấn đề như vậy? Tất nhiên, tôi có thể định nghĩa lại hàm là:

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

Tuy nhiên, điều gì sẽ xảy ra nếu tôi không được phép thao tác chức năng ban đầu hoặc nếu có hàng ngàn chức năng như vậy được xử lý.

Giải quyết vấn đề bằng cách suy nghĩ khác biệt và xác định hàm mới

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

Sau đó sử dụng nó.

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'

Tada, bạn thấy đấy, tôi đã sửa đổi read_a_bookmà không cần chạm vào nó. Không có gì ngăn cản tôi trang bị decorator.

Những gì về @

@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_booklà một cách thú vị và tiện dụng để nói read_a_book = add_a_book(read_a_book), đó là một cú pháp cú pháp, không có gì lạ hơn về nó.


16

Nếu bạn đang đề cập đến một số mã trong sổ ghi chép python đang sử dụng thư viện Numpy , thì @ operatorcó nghĩa là Phép nhân ma trận . Ví dụ:

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


6

Các trình trang trí đã được thêm vào Python để làm cho hàm và phương thức gói (một hàm nhận hàm và trả về một hàm nâng cao) dễ đọc và dễ hiểu hơn. Trường hợp sử dụng ban đầu là có thể định nghĩa các phương thức là phương thức lớp hoặc phương thức tĩnh trên đầu định nghĩa của chúng. Nếu không có cú pháp trang trí, nó sẽ đòi hỏi một định nghĩa khá thưa thớt và lặp đi lặp lại:

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)

Nếu cú ​​pháp trang trí được sử dụng cho cùng một mục đích, mã sẽ ngắn hơn và dễ hiểu hơn:

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

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

Cú pháp chung và các triển khai có thể

Trình trang trí nói chung là một đối tượng được đặt tên ( biểu thức lambda không được phép ) chấp nhận một đối số duy nhất khi được gọi (nó sẽ là hàm trang trí) và trả về một đối tượng có thể gọi khác. "Callable" được sử dụng ở đây thay vì "chức năng" với tiền xử lý. Mặc dù các nhà trang trí thường được thảo luận trong phạm vi của các phương thức và chức năng, nhưng chúng không giới hạn ở chúng. Trong thực tế, bất kỳ thứ gì có thể gọi được (bất kỳ đối tượng nào thực hiện phương thức _call__ đều được coi là có thể gọi được), có thể được sử dụng như một công cụ trang trí và thường các đối tượng được trả về bởi chúng không phải là các hàm đơn giản mà là nhiều trường hợp của các lớp phức tạp hơn thực hiện phương thức __call_ của riêng chúng.

Cú pháp trang trí chỉ đơn giản là một đường cú pháp . Hãy xem xét cách sử dụng trang trí sau đây:

@some_decorator
def decorated_function():
    pass

Điều này luôn có thể được thay thế bằng một cuộc gọi trang trí rõ ràng và phân công lại chức năng:

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

Tuy nhiên, cái sau ít đọc hơn và cũng rất khó hiểu nếu nhiều bộ trang trí được sử dụng trên một chức năng. Trang trí có thể được sử dụng theo nhiều cách khác nhau như dưới đây:

Là một chức năng

Có nhiều cách để viết các trang trí tùy chỉnh, nhưng cách đơn giản nhất là viết một hàm trả về một hàm con bao bọc lệnh gọi hàm ban đầu.

Các mẫu chung như sau:

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

Như một lớp học

Mặc dù các trình trang trí hầu như luôn có thể được thực hiện bằng các hàm, có một số tình huống khi sử dụng các lớp do người dùng định nghĩa là một lựa chọn tốt hơn. Điều này thường đúng khi người trang trí cần tham số phức tạp hoặc nó phụ thuộc vào một trạng thái cụ thể.

Mẫu chung cho một trang trí không bị biến dạng như một lớp như sau:

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

Trang trí tham số

Trong mã thực, thường có nhu cầu sử dụng các trang trí có thể được tham số hóa. Khi chức năng được sử dụng như một công cụ trang trí, thì giải pháp đơn giản là một cấp độ gói thứ hai phải được sử dụng. Dưới đây là một ví dụ đơn giản về trình trang trí lặp lại việc thực hiện chức năng được trang trí số lần chỉ định mỗi lần nó được gọi:

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

Trình trang trí được định nghĩa theo cách này có thể chấp nhận các tham số:

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

Lưu ý rằng ngay cả khi trình trang trí tham số có các giá trị mặc định cho các đối số của nó, các dấu ngoặc đơn sau tên của nó là bắt buộc. Cách chính xác để sử dụng trình trang trí trước với các đối số mặc định như sau:

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

Cuối cùng, hãy xem trang trí với Properties.

Tính chất

Các thuộc tính cung cấp một kiểu mô tả tích hợp , biết cách liên kết một thuộc tính với một tập hợp các phương thức. Một thuộc tính có bốn đối số tùy chọn: fget, fset, fdel và doc. Cái cuối cùng có thể được cung cấp để xác định một chuỗi doc được liên kết với thuộc tính như thể nó là một phương thức. Dưới đây là một ví dụ về lớp Hình chữ nhật có thể được kiểm soát bằng cách truy cập trực tiếp vào các thuộc tính lưu trữ hai điểm góc hoặc bằng cách sử dụng các thuộc tính chiều rộng và chiều cao:

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
    )

Cú pháp tốt nhất để tạo thuộc tính là sử dụng thuộc tính làm trang trí. Điều này sẽ làm giảm số lượng chữ ký phương thức bên trong lớp và làm cho mã dễ đọc và dễ bảo trì hơn . Với các nhà trang trí, lớp trên trở thành:

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

2

Để nói những gì người khác có theo một cách khác: vâng, đó là một trang trí.

Trong Python, nó giống như:

  1. Tạo một chức năng (theo cuộc gọi @)
  2. Gọi một chức năng khác để hoạt động trên chức năng đã tạo của bạn. Điều này trả về một chức năng mới. Hàm mà bạn gọi là đối số của @.
  3. Thay thế chức năng được xác định bằng chức năng mới được trả về.

Điều này có thể được sử dụng cho tất cả các loại điều hữu ích, được thực hiện bởi vì các hàm là các đối tượng và chỉ cần các hướng dẫn.


2

biểu tượng @ cũng được sử dụng để truy cập các biến trong truy vấn khung dữ liệu plydata / pandas , pandas.DataFrame.query. Thí dụ:

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.