Cosa fa il simbolo "at" (@) in Python?


580

Sto guardando del codice Python che utilizzava il @simbolo, ma non ho idea di cosa faccia. Inoltre non so cosa cercare poiché la ricerca di documenti Python o Google non restituisce risultati pertinenti quando @è incluso il simbolo.

Risposte:


305

Un @simbolo all'inizio di una riga viene utilizzato per decoratori di classi, funzioni e metodi .

Leggi di più qui:

PEP 318: Decoratori

Decoratori Python

I più comuni decoratori di Python in cui ti imbatterai sono:

@proprietà

@classmethod

@staticmethod

Se vedi un @al centro di una linea, è una cosa diversa, la moltiplicazione della matrice. Scorri verso il basso per vedere altre risposte che affrontano quell'uso di @.


31
Sembra che possa anche essere un operatore di moltiplicazione di matrici: stackoverflow.com/a/21563036/5049813
Pro Q

@decorators può anche essere aggiunto
Vijay Panchal

349

Esempio

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

Ciò mostra che il function/ method/ classche stai definendo dopo che un decoratore è sostanzialmente passato argumental function/ methodimmediatamente dopo il @segno.

Primo avvistamento

Il pallone microframe introduce i decoratori fin dall'inizio nel seguente formato:

from flask import Flask
app = Flask(__name__)

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

Questo a sua volta si traduce in:

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

Capire questo alla fine mi ha permesso di sentirmi in pace con Flask.


7
Nel caso di Flasks app.route("/"): questa funzione restituisce una funzione, che invochi con il tuo hello()come argomento
shaqed

3
Qual è il vantaggio sintattico o pratico di avere qui i decoratori, invece di (per esempio) chiamare semplicemente qualcosa come app.route("/", hello)immediatamente dopo aver definito hello, o persino definire hellocome lambda negli argomenti a app.route? (Quest'ultimo esempio è comune con le http.Serverrotte Node.js ed Express.)
iono il

186

Questo frammento di codice:

def decorator(func):
   return func

@decorator
def some_func():
    pass

È equivalente a questo codice:

def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)

Nella definizione di decoratore puoi aggiungere alcune cose modificate che non verrebbero normalmente restituite da una funzione.


1
In questa riga s "ome_func = decorator (some_func)", il primo some_func è una variabile = per la funzione some_func, giusto?
Viragos,

147

In Python 3.5 puoi sovraccaricare @come operatore. È chiamato come __matmul__, perché è progettato per eseguire la moltiplicazione di matrici, ma può essere qualsiasi cosa tu voglia. Vedi PEP465 per i dettagli.

Questa è una semplice implementazione della moltiplicazione di matrici.

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)

Questo codice produce:

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

14
Hai anche l' @=operatore (sul posto), che è __imatmul__.
Pål GD,

Ci sono altri operatori scavalcabili come questo? Conosco __add__e __sub__sono collegati rispettivamente a + e -, ma non ho mai sentito parlare del @segno prima. Ce ne sono altri in agguato là fuori?
Thomas Kimber,

103

Cosa fa il simbolo "at" (@) in Python?

In breve, viene utilizzato nella sintassi del decoratore e per la moltiplicazione della matrice.

Nel contesto dei decoratori, questa sintassi:

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

è equivalente a questo:

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

decorated_function = decorator(decorated_function)

Nel contesto della moltiplicazione di matrici, a @ binvoca a.__matmul__(b)- creando questa sintassi:

a @ b

equivalente a

dot(a, b)

e

a @= b

equivalente a

a = dot(a, b)

dove si dottrova, ad esempio, la funzione di moltiplicazione della matrice numpy e ae bsono matrici.

Come hai potuto scoprirlo da solo?

Inoltre non so cosa cercare poiché la ricerca di documenti Python o Google non restituisce risultati pertinenti quando è incluso il simbolo @.

Se vuoi avere una visione piuttosto completa di ciò che fa una particolare sintassi di Python, guarda direttamente il file di grammatica. Per il ramo 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

Possiamo vedere qui che @viene utilizzato in tre contesti:

  • decoratori
  • un operatore tra fattori
  • un operatore di assegnazione aumentato

Sintassi del decoratore:

Una ricerca su Google per "decorator python docs" fornisce come uno dei migliori risultati la sezione "Dichiarazioni composte" della "Guida di riferimento di Python". Scorrendo verso il basso fino alla sezione sulle definizioni delle funzioni , che possiamo trovare cercando la parola "decoratore", vediamo che ... c'è molto da leggere. Ma la parola "decoratore" è un collegamento al glossario , che ci dice:

decoratore

Una funzione che restituisce un'altra funzione, generalmente applicata come trasformazione di una funzione usando la @wrappersintassi. Esempi comuni per decoratori sono classmethod()estaticmethod() .

La sintassi del decoratore è semplicemente zucchero sintattico, le seguenti due definizioni di funzione sono semanticamente equivalenti:

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

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

Lo stesso concetto esiste per le classi, ma è meno comunemente usato lì. Consulta la documentazione per le definizioni delle funzioni e le definizioni delle classi per ulteriori informazioni sui decoratori.

Quindi, lo vediamo

@foo
def bar():
    pass

è semanticamente uguale a:

def bar():
    pass

bar = foo(bar)

Non sono esattamente gli stessi perché Python valuta l'espressione foo (che potrebbe essere una ricerca tratteggiata e una chiamata di funzione) prima della barra con la @sintassi di decorator ( ), ma valuta l'espressione foo dopo la barra nell'altro caso.

(Se questa differenza fa la differenza nel significato del tuo codice, dovresti riconsiderare cosa stai facendo della tua vita, perché sarebbe patologico.)

Decoratori impilati

Se torniamo alla documentazione sulla sintassi della definizione della funzione, vediamo:

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

è approssimativamente equivalente a

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

Questa è una dimostrazione che possiamo chiamare una funzione che è prima un decoratore, così come i decoratori dello stack. Le funzioni, in Python, sono oggetti di prima classe, il che significa che puoi passare una funzione come argomento a un'altra funzione e restituire funzioni. I decoratori fanno entrambe queste cose.

Se impiliamo i decoratori, la funzione, come definita, viene passata prima al decoratore immediatamente sopra di esso, quindi al successivo e così via.

Quello su riassume l'uso per @nel contesto dei decoratori.

L'operatore, @

Nella sezione di analisi lessicale del riferimento linguistico, abbiamo una sezione sugli operatori , che include @, che lo rende anche un operatore:

I seguenti token sono operatori:

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

e nella pagina successiva, il modello di dati, abbiamo la sezione Emulazione di tipi numerici ,

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

[...] Questi metodi sono chiamati ad attuare le operazioni aritmetiche binarie ( +, -, *, @, /, //, [...]

E vediamo che __matmul__corrisponde a @. Se cerchiamo la documentazione per "matmul" otteniamo un collegamento a Novità di Python 3.5 con "matmul" sotto la voce "PEP 465 - Un operatore infix dedicato per la moltiplicazione di matrici".

può essere implementato definendo __matmul__(), __rmatmul__()e __imatmul__()per la moltiplicazione della matrice regolare, riflessa e sul posto.

(Quindi ora apprendiamo che @=è la versione sul posto). Spiega inoltre:

La moltiplicazione delle matrici è un'operazione particolarmente comune in molti campi della matematica, della scienza, dell'ingegneria e l'aggiunta di @ consente di scrivere codice più pulito:

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

invece di:

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

Mentre questo operatore può essere sovraccaricato per fare praticamente qualsiasi cosa, numpyad esempio, utilizzeremmo questa sintassi per calcolare il prodotto interno ed esterno di matrici e matrici:

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

Moltiplicazione della matrice inplace: @=

Durante la ricerca dell'uso precedente, apprendiamo che esiste anche la moltiplicazione della matrice sul posto. Se tentiamo di usarlo, potremmo scoprire che non è ancora implementato per 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'.

Quando viene implementato, mi aspetto che il risultato sia simile al seguente:

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

36

Cosa fa il simbolo "at" (@) in Python?

Il simbolo @ è un pitone dello zucchero sintattico da utilizzare decorator,
per parafrasare la domanda, riguarda esattamente cosa fa il decoratore in Python?

In parole semplici, decoratorconsente di modificare la definizione di una determinata funzione senza toccarne l'interno (è la chiusura).
È il caso più importante quando si importano meravigliosi pacchetti da terze parti. Puoi visualizzarlo, puoi usarlo, ma non puoi toccarne il più profondo e il suo cuore.

Ecco un breve esempio,
supponiamo che io definisca una read_a_bookfunzione su 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: '

Vedi, ho dimenticato di aggiungere un nome.
Come risolvere un simile problema? Certo, potrei ridefinire la funzione come:

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

Tuttavia, cosa succede se non mi è permesso manipolare la funzione originale o se ci sono migliaia di tale funzione da gestire.

Risolvi il problema pensando diversamente e definendo una nuova funzione

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

Quindi impiegalo.

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, vedi, ho modificato read_a_booksenza toccarlo chiusura interna. Niente mi impedisce di essere equipaggiato decorator.

Di cosa si tratta @

@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è un modo elegante e pratico per dire read_a_book = add_a_book(read_a_book), è uno zucchero sintattico, non c'è niente di più fantasioso al riguardo.


16

Se ti riferisci ad un codice in un notebook Python che utilizza la libreria Numpy , allora @ operatorsignifica Moltiplicazione di matrici . Per esempio:

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

I decoratori sono stati aggiunti in Python per semplificare la lettura e la comprensione del wrapping di funzioni e metodi (una funzione che riceve una funzione e ne restituisce una migliorata). Il caso d'uso originale doveva essere in grado di definire i metodi come metodi di classe o metodi statici sulla testa della loro definizione. Senza la sintassi del decoratore, richiederebbe una definizione piuttosto sparsa e ripetitiva:

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)

Se la sintassi del decoratore viene utilizzata per lo stesso scopo, il codice è più breve e più facile da capire:

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

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

Sintassi generale e possibili implementazioni

Il decoratore è generalmente un oggetto con nome ( non sono ammesse espressioni lambda ) che accetta un singolo argomento quando viene chiamato (sarà la funzione decorata) e restituisce un altro oggetto richiamabile. "Callable" è usato qui invece di "funzione" con premeditazione. Mentre i decoratori sono spesso discussi nell'ambito dei metodi e delle funzioni, non si limitano a loro. In effetti, tutto ciò che è richiamabile (qualsiasi oggetto che implementa il metodo _call__ è considerato richiamabile), può essere usato come decoratore e spesso gli oggetti restituiti da loro non sono semplici funzioni ma più istanze di classi più complesse che implementano il proprio metodo __call_.

La sintassi del decoratore è semplicemente solo uno zucchero sintattico . Considera il seguente utilizzo del decoratore:

@some_decorator
def decorated_function():
    pass

Questo può sempre essere sostituito da una chiamata esplicativa del decoratore e dalla riassegnazione della funzione:

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

Tuttavia, quest'ultimo è meno leggibile e anche molto difficile da capire se su una singola funzione vengono utilizzati più decoratori. I decoratori possono essere utilizzati in diversi modi come mostrato di seguito:

Come una funzione

Esistono molti modi per scrivere decoratori personalizzati, ma il modo più semplice è scrivere una funzione che restituisce una sottofunzione che avvolge la chiamata di funzione originale.

I modelli generici sono i seguenti:

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

Come una classe

Mentre i decoratori possono quasi sempre essere implementati usando le funzioni, ci sono alcune situazioni in cui l'uso delle classi definite dall'utente è un'opzione migliore. Questo è spesso vero quando il decoratore ha bisogno di una parametrizzazione complessa o dipende da uno stato specifico.

Il modello generico per un decoratore non parametrizzato come classe è il seguente:

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

Parametrizzare i decoratori

Nel codice reale, è spesso necessario utilizzare decoratori che possono essere parametrizzati. Quando la funzione viene utilizzata come decoratore, la soluzione è semplice: è necessario utilizzare un secondo livello di avvolgimento. Ecco un semplice esempio del decoratore che ripete l'esecuzione di una funzione decorata il numero specificato di volte ogni volta che viene chiamato:

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

Il decoratore definito in questo modo può accettare parametri:

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

Si noti che anche se il decoratore parametrizzato ha valori predefiniti per i suoi argomenti, sono necessarie le parentesi dopo il suo nome. Il modo corretto di utilizzare il decoratore precedente con argomenti predefiniti è il seguente:

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

Finalmente vediamo i decoratori con Proprietà.

Proprietà

Le proprietà forniscono un tipo di descrittore incorporato che sa come collegare un attributo a una serie di metodi. Una proprietà accetta quattro argomenti opzionali: fget, fset, fdel e doc. L'ultimo può essere fornito per definire una docstring collegata all'attributo come se fosse un metodo. Ecco un esempio di una classe Rectangle che può essere controllata mediante l'accesso diretto agli attributi che memorizzano due punti d'angolo o usando le proprietà width e 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
    )

La migliore sintassi per la creazione di proprietà è l'utilizzo della proprietà come decoratore. Ciò ridurrà il numero di firme dei metodi all'interno della classe e renderà il codice più leggibile e gestibile . Con i decoratori la classe sopra diventa:

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

Per dire ciò che gli altri hanno in un modo diverso: sì, è un decoratore.

In Python, è come:

  1. Creazione di una funzione (segue sotto la chiamata @)
  2. Chiamare un'altra funzione per operare sulla funzione creata. Ciò restituisce una nuova funzione. La funzione chiamata è l'argomento di @.
  3. Sostituzione della funzione definita con la nuova funzione restituita.

Questo può essere usato per tutti i tipi di cose utili, reso possibile perché le funzioni sono oggetti e solo le necessarie istruzioni.


2

Il simbolo @ viene anche utilizzato per accedere alle variabili all'interno di una query del frame di dati plydata / pandas, pandas.DataFrame.query . Esempio:

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.