Co robi symbol „at” (@) w Pythonie?

578

Patrzę na jakiś kod Pythona, który używał tego @symbolu, ale nie mam pojęcia, co on robi. Nie wiem też, czego szukać, ponieważ wyszukiwanie dokumentów w języku Python lub Google nie zwraca odpowiednich wyników, gdy ten @symbol jest dołączony.

AJ00200
źródło

Odpowiedzi:

303

@Symbol na początku linii jest używany do klasy, funkcje i sposób dekoratorów .

Przeczytaj więcej tutaj:

PEP 318: Dekoratorzy

Dekoratory Python

Najczęstszymi dekoratorami Pythona, na które natrafisz, są:

@własność

@classmethod

@staticmethod

Jeśli widzisz @na środku linii, to inna rzecz, mnożenie macierzy. Przewiń w dół, aby zobaczyć inne odpowiedzi dotyczące tego zastosowania @.

FogleBird
źródło
31
Wygląda na to, że może być również operatorem mnożenia macierzy: stackoverflow.com/a/21563036/5049813
Pro Q
Można również dodać
@decorators
346

Przykład

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

To pokazuje, że function/ method/, classktóre definiujesz po dekoratorze, jest po prostu przekazywane argumentdo function/ methodbezpośrednio po @znaku.

Pierwsze spojrzenie

Mikroframa Flask wprowadza dekoratorów od samego początku w następującym formacie:

from flask import Flask
app = Flask(__name__)

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

To z kolei przekłada się na:

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

Uświadomienie sobie tego wreszcie pozwoliło mi poczuć się spokojnie z Flask.

Morgan Wilde
źródło
7
W przypadku Flasksa app.route("/"): ta funkcja zwraca funkcję, którą wywołujesz hello()jako argument
shaqed
3
Jaka jest składniowa lub praktyczna korzyść z posiadania dekoratorów tutaj, zamiast (na przykład) wywołania czegoś takiego jak app.route("/", hello)natychmiast po zdefiniowaniu hello, a nawet zdefiniowania hellojako lambda w argumentach app.route? (Drugi przykład jest wspólny dla http.Servertras Node.js i Express).
iono
185

Ten fragment kodu:

def decorator(func):
   return func

@decorator
def some_func():
    pass

Jest równoważny z tym kodem:

def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)

W definicji dekoratora możesz dodać zmodyfikowane rzeczy, które normalnie nie byłyby zwracane przez funkcję.

Matheus Araujo
źródło
1
W tym wierszu „ome_func = dekorator (some_func)”, pierwszy some_func jest zmienną = dla funkcji some_func, prawda?
Viragos,
147

W Pythonie 3.5 można przeciążać @jako operator. Nazywa się tak __matmul__, ponieważ jest przeznaczony do mnożenia macierzy, ale może być czymkolwiek chcesz. Szczegóły znajdują się w PEP465 .

Jest to prosta implementacja mnożenia macierzy.

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)

Ten kod daje:

[[18, 14], [62, 66]]
jinhwanlazy
źródło
14
Masz również @=operator (lokalny), czyli __imatmul__.
Pål GD
Czy istnieją inne podobne do siebie operatory? Wiem __add__i jestem __sub__powiązany odpowiednio z + i -, ale nigdy wcześniej nie słyszałem o @znaku. Czy są tam jeszcze inni?
Thomas Kimber
103

Co robi symbol „at” (@) w Pythonie?

W skrócie, jest on używany w składni dekoratora i do mnożenia macierzy.

W kontekście dekoratorów ta składnia:

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

jest równoważne z tym:

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

decorated_function = decorator(decorated_function)

W kontekście mnożenia macierzy a @ bwywołuje a.__matmul__(b)- czyniąc tę ​​składnię:

a @ b

równoważny

dot(a, b)

i

a @= b

równoważny

a = dot(a, b)

gdzie dotjest, na przykład, funkcja mnożenia macierzy numpy ai bsą macierzami.

Jak mogłeś to odkryć na własną rękę?

Nie wiem też, czego szukać, ponieważ wyszukiwanie dokumentów w języku Python lub Google nie zwraca odpowiednich wyników, gdy dołączony jest symbol @.

Jeśli chcesz mieć dość pełny obraz tego, co robi konkretna część składni Pythona, spójrz bezpośrednio na plik gramatyki. W przypadku gałęzi 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

Widzimy tutaj, który @jest używany w trzech kontekstach:

  • dekoratorzy
  • operator między czynnikami
  • operator przydziału rozszerzonego

Składnia dekoratora:

Wyszukiwarka google „dekorator python docs” podaje jako jeden z najlepszych wyników sekcję „Instrukcje złożone” w „Python Language Reference”. Przewijając w dół do sekcji definicji funkcji , którą możemy znaleźć, szukając słowa „dekorator”, widzimy, że… jest wiele do przeczytania. Ale słowo „dekorator” to link do glosariusza , który mówi nam:

dekorator

Funkcja zwracająca inną funkcję, zwykle stosowana jako transformacja funkcji przy użyciu @wrapperskładni. Typowymi przykładami dekoratorów są classmethod()i staticmethod().

Składnia dekoratora jest jedynie cukrem syntaktycznym, następujące dwie definicje funkcji są semantycznie równoważne:

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

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

Ta sama koncepcja istnieje dla klas, ale jest tam rzadziej stosowana. Więcej informacji na temat dekoratorów można znaleźć w dokumentacji definicji funkcji i definicji klas.

Widzimy to

@foo
def bar():
    pass

jest semantycznie taki sam jak:

def bar():
    pass

bar = foo(bar)

Nie są one dokładnie takie same, ponieważ Python ocenia wyrażenie foo (które może być wyszukiwaniem kropkowym i wywołaniem funkcji) przed słupkiem za pomocą @składni decorator ( ), ale w innym przypadku ocenia wyrażenie foo po słupku.

(Jeśli ta różnica ma znaczenie w znaczeniu twojego kodu, powinieneś ponownie rozważyć to, co robisz ze swoim życiem, ponieważ byłoby to patologiczne).

Ułożone dekoratorów

Jeśli wrócimy do dokumentacji składni definicji funkcji, zobaczymy:

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

jest mniej więcej równoważne z

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

Jest to demonstracja, że ​​możemy wywołać funkcję, która najpierw jest dekoratorem, a także dekoratorami stosu. Funkcje w Pythonie są obiektami pierwszej klasy - co oznacza, że ​​możesz przekazać funkcję jako argument do innej funkcji i zwrócić funkcje. Dekoratorzy robią obie te rzeczy.

Jeśli ułożymy dekoratory w stos, funkcja, zgodnie z definicją, zostanie najpierw przekazana dekoratorowi bezpośrednio nad nią, a następnie następna i tak dalej.

To wszystko podsumowuje użycie @w kontekście dekoratorów.

Operator @

W sekcji analizy leksykalnej odwołania do języka mamy sekcję dotyczącą operatorów , która zawiera @, co czyni ją również operatorem:

Następujące tokeny są operatorami:

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

a na następnej stronie Model danych mamy sekcję Emulowanie typów numerycznych ,

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

[...] Te metody są powołani do wdrożenia binarnych operacji arytmetycznych ( +, -, *, @, /, //, [...]

I widzimy, że to __matmul__odpowiada @. Jeśli przeszukamy dokumentację pod kątem „matmul”, otrzymujemy link do Co nowego w Pythonie 3.5 z „matmul” pod nagłówkiem „PEP 465 - Dedykowany operator infix do mnożenia macierzy”.

może być realizowane poprzez zdefiniowanie __matmul__(), __rmatmul__()oraz __imatmul__()dla regularnej, odbicie, a w miejscu mnożenia macierzy.

(Więc teraz dowiadujemy się, że @=jest to wersja na miejscu). Wyjaśnia ponadto:

Mnożenie macierzy jest szczególnie powszechną operacją w wielu dziedzinach matematyki, nauki, inżynierii, a dodanie @ pozwala na pisanie czystszego kodu:

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

zamiast:

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

Podczas gdy ten operator może być przeciążony, aby zrobić prawie wszystko, numpyna przykład użylibyśmy tej składni do obliczenia wewnętrznego i zewnętrznego produktu tablic i macierzy:

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

Mnożenie macierzy w miejscu: @=

Badając wcześniejsze użycie, dowiadujemy się, że istnieje również wewnętrzne mnożenie macierzy. Jeśli spróbujemy go użyć, może się okazać, że nie jest jeszcze zaimplementowany dla 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'.

Po wdrożeniu oczekiwałbym, że wynik będzie wyglądał następująco:

>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])
Aaron Hall
źródło
36

Co robi symbol „at” (@) w Pythonie?

Symbol @ to syntaktyczny python z cukrem, który można wykorzystać decorator,
aby sparafrazować pytanie: To dokładnie o tym, co robi dekorator w Pythonie?

Upraszczając, decoratormożesz zmodyfikować definicję danej funkcji bez dotykania jej od środka (jej zamknięcia).
Jest to najbardziej przypadek, gdy importujesz wspaniały pakiet od strony trzeciej. Możesz to sobie wyobrazić, możesz z niego korzystać, ale nie możesz dotknąć jego wnętrza i serca.

Oto szybki przykład,
załóżmy , że zdefiniowałem read_a_bookfunkcję na 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: '

Widzisz, zapomniałem dodać do niego nazwę.
Jak rozwiązać taki problem? Oczywiście mógłbym ponownie zdefiniować funkcję jako:

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

Niemniej jednak, jeśli nie wolno mi manipulować oryginalną funkcją lub jeśli istnieją tysiące takich funkcji do obsłużenia.

Rozwiąż problem, myśląc inaczej i zdefiniuj nową funkcję

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

Więc zastosuj to.

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, widzicie, poprawiłem read_a_bookbez dotykania jej wewnętrznego zamknięcia. Nic mnie nie powstrzyma decorator.

O co chodzi @

@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_bookto fantazyjny i wygodny sposób na stwierdzenie read_a_book = add_a_book(read_a_book), że to cukier składniowy, nie ma w tym nic bardziej wyszukanego.

Rachunek różniczkowy
źródło
16

Jeśli odwołujesz się do jakiegoś kodu w notatniku Pythona, który korzysta z biblioteki Numpy , @ operatoroznacza to mnożenie macierzy . Na przykład:

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
f__society
źródło
6

W Pythonie dodano dekoratory, aby łatwiej było czytać i rozumieć zawijanie funkcji i metod (funkcja, która odbiera funkcję i zwraca ulepszoną). Pierwotny przypadek użycia miał być w stanie zdefiniować metody jako metody klasowe lub metody statyczne na początku ich definicji. Bez składni dekoratora wymagałoby to raczej rzadkiej i powtarzalnej definicji:

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)

Jeśli składnia dekoratora jest używana w tym samym celu, kod jest krótszy i łatwiejszy do zrozumienia:

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

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

Ogólna składnia i możliwe implementacje

Dekorator jest generalnie nazwanym obiektem ( wyrażenia lambda nie są dozwolone ), który akceptuje pojedynczy argument po wywołaniu (będzie to funkcja dekorowana) i zwraca inny obiekt możliwy do wywołania. Używa się tutaj „wywoływalnego” zamiast „funkcji” z premedytacją. Podczas gdy dekoratorzy są często dyskutowani w zakresie metod i funkcji, nie są do nich ograniczeni. W rzeczywistości wszystko, co można wywoływać (każdy obiekt, który implementuje metodę _call__ jest uważany za wywoływalny), może być używane jako dekorator, a często zwracane przez nie obiekty nie są prostymi funkcjami, ale bardziej instancjami bardziej złożonych klas implementujących własną metodę __call_.

Składnia dekoratora to po prostu tylko cukier składniowy . Rozważ następujące użycie dekoratora:

@some_decorator
def decorated_function():
    pass

Zawsze można to zastąpić jawnym wywołaniem dekoratora i ponownym przypisaniem funkcji:

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

Jednak ta ostatnia jest mniej czytelna, a także bardzo trudna do zrozumienia, jeśli na jednej funkcji użyto wielu dekoratorów. Dekoratorów można używać na wiele różnych sposobów, jak pokazano poniżej:

Jako funkcja

Istnieje wiele sposobów pisania niestandardowych dekoratorów, ale najprostszym sposobem jest napisanie funkcji, która zwraca podfunkcję, która otacza oryginalne wywołanie funkcji.

Ogólne wzorce są następujące:

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

Jako klasa

Chociaż dekoratory prawie zawsze można zaimplementować za pomocą funkcji, w niektórych sytuacjach lepszym rozwiązaniem jest użycie klas zdefiniowanych przez użytkownika. Jest to często prawdziwe, gdy dekorator wymaga złożonej parametryzacji lub zależy to od określonego stanu.

Ogólny wzór dla niesparametryzowanego dekoratora jako klasy jest następujący:

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

Parametryzacja dekoratorów

W prawdziwym kodzie często zachodzi potrzeba użycia dekoratorów, które można sparametryzować. Gdy funkcja jest używana jako dekorator, wówczas rozwiązanie jest proste - należy zastosować drugi poziom owijania. Oto prosty przykład dekoratora, który powtarza wykonanie dekorowanej funkcji określoną liczbę razy za każdym razem, gdy jest wywoływana:

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

Dekorator zdefiniowany w ten sposób może akceptować parametry:

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

Zauważ, że nawet jeśli sparametryzowany dekorator ma domyślne wartości dla swoich argumentów, wymagane są nawiasy po nazwie. Prawidłowy sposób użycia poprzedniego dekoratora z domyślnymi argumentami jest następujący:

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

Wreszcie pozwala zobaczyć dekoratorów z właściwościami.

Nieruchomości

Właściwości zapewniają wbudowany typ deskryptora, który wie, jak połączyć atrybut z zestawem metod. Właściwość przyjmuje cztery opcjonalne argumenty: fget, fset, fdel i doc. Ostatni można podać w celu zdefiniowania ciągu dokumentów, który jest powiązany z atrybutem tak, jakby to była metoda. Oto przykład klasy Rectangle, którą można kontrolować poprzez bezpośredni dostęp do atrybutów przechowujących dwa punkty narożne lub za pomocą właściwości width i height:

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
    )

Najlepszą składnią do tworzenia właściwości jest użycie właściwości jako dekoratora. Spowoduje to zmniejszenie liczby podpisów metod wewnątrz klasy i sprawi, że kod będzie bardziej czytelny i łatwy w utrzymaniu . Dzięki dekoratorom powyższa klasa staje się:

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
iun1x
źródło
2

Powiedzieć, co mają inni w inny sposób: tak, to dekorator.

W Pythonie wygląda to tak:

  1. Tworzenie funkcji (pod wywołaniem @)
  2. Wywoływanie innej funkcji w celu obsługi utworzonej funkcji. To zwraca nową funkcję. Wywołana funkcja jest argumentem @.
  3. Zwrócono funkcję zdefiniowaną przez nową funkcję.

Można to wykorzystać do wszelkiego rodzaju przydatnych rzeczy, co jest możliwe, ponieważ funkcje są obiektami i tylko niezbędnymi instrukcjami.

Mayur Patel
źródło
2

@ Symbol jest również stosowany do zmiennych dostępu w plydata / pandy dataframe kwerendy pandas.DataFrame.query. Przykład:

df = pandas.DataFrame({'foo': [1,2,15,17]})
y = 10
df >> query('foo > @y') # plydata
df.query('foo > @y') # pandas
Aswin
źródło