Odpowiedzi:
@
Symbol na początku linii jest używany do klasy, funkcje i sposób dekoratorów .
Przeczytaj więcej tutaj:
Najczęstszymi dekoratorami Pythona, na które natrafisz, są:
Jeśli widzisz @
na środku linii, to inna rzecz, mnożenie macierzy. Przewiń w dół, aby zobaczyć inne odpowiedzi dotyczące tego zastosowania @
.
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
/, class
które definiujesz po dekoratorze, jest po prostu przekazywane argument
do function
/ method
bezpośrednio po @
znaku.
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.
app.route("/")
: ta funkcja zwraca funkcję, którą wywołujesz hello()
jako argument
app.route("/", hello)
natychmiast po zdefiniowaniu hello
, a nawet zdefiniowania hello
jako lambda w argumentach app.route
? (Drugi przykład jest wspólny dla http.Server
tras Node.js i Express).
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ę.
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]]
@=
operator (lokalny), czyli __imatmul__
.
__add__
i jestem __sub__
powiązany odpowiednio z + i -, ale nigdy wcześniej nie słyszałem o @
znaku. Czy są tam jeszcze inni?
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 @ b
wywoł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 dot
jest, na przykład, funkcja mnożenia macierzy numpy a
i b
są macierzami.
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:
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
@wrapper
składni. Typowymi przykładami dekoratorów sąclassmethod()
istaticmethod()
.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).
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.
@
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, numpy
na 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]])
@=
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]])
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, decorator
moż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_book
funkcję 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_book
bez 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_book
to 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.
Jeśli odwołujesz się do jakiegoś kodu w notatniku Pythona, który korzysta z biblioteki Numpy , @ operator
oznacza 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
Począwszy od Python 3.5, „@” jest używany jako dedykowany symbol poprawki dla MATRIX MULTIPLICATION (PEP 0465 - patrz https://www.python.org/dev/peps/pep-0465/ )
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
Powiedzieć, co mają inni w inny sposób: tak, to dekorator.
W Pythonie wygląda to tak:
Można to wykorzystać do wszelkiego rodzaju przydatnych rzeczy, co jest możliwe, ponieważ funkcje są obiektami i tylko niezbędnymi instrukcjami.
Wskazuje, że używasz dekoratora. Oto przykład Bruce'a Eckela z 2008 roku.