Je regarde du code Python qui utilisait le symbole @
, mais je n'ai aucune idée de ce qu'il fait. Je ne sais pas non plus quoi rechercher en tant que recherche Python docs ou Google ne renvoie pas de résultats pertinents lorsque le symbole @
est inclus.
Un symbole @
au début d'une ligne est utilisé pour les décorateurs de classe, fonction et méthode .
Lire la suite ici:
Les décorateurs les plus courants Python que vous rencontrerez sont:
Si vous voyez un @
au milieu d'une ligne, c'est différent, la multiplication matricielle. Faites défiler la liste pour voir d'autres réponses qui traitent de l'utilisation de @
.
J'admets qu'il m'a fallu plus de quelques instants pour saisir pleinement ce concept, alors je vais partager ce que j'ai appris pour sauver les autres.
Le nom décorateur - la chose que nous définissons en utilisant la syntaxe @
avant une définition de fonction - était probablement le principal responsable ici.
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']
Cela montre que la function
/method
/class
que vous définissez après un décorateur est simplement passée en tant que argument
à la function
/method
immédiatement après le signe @
.
Le microframework Flask introduit décorateurs depuis le début dans le format suivant:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
Cela se traduit par:
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
Réaliser cela m'a enfin permis de me sentir en paix avec Flask.
Cet extrait de code:
def decorator(func):
return func
@decorator
def some_func():
pass
Est équivalent à ce code:
def decorator(func):
return func
def some_func():
pass
some_func = decorator(some_func)
Dans la définition d'un décorateur, vous pouvez ajouter des éléments modifiés qui ne seraient pas renvoyés normalement par une fonction.
Dans Python 3.5, vous pouvez surcharger @
en tant qu'opérateur. Il est nommé __matmul__
, car il est conçu pour effectuer la multiplication matricielle, mais peut être celui que vous souhaitiez. Voir PEP465 pour plus de détails.
Ceci est une simple implémentation de la multiplication matricielle.
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)
Ce code donne:
[[18, 14], [62, 66]]
En bref, il est utilisé dans la syntaxe de décorateur et pour la multiplication de matrices.
Dans le contexte des décorateurs, cette syntaxe:
@decorator
def decorated_function():
"""this function is decorated"""
est équivalent à ceci:
def decorated_function():
"""this function is decorated"""
decorated_function = decorator(decorated_function)
Dans le contexte de la multiplication de matrice, a @ b
appelle a.__matmul__(b)
- en créant cette syntaxe:
a @ b
équivalent à
dot(a, b)
et
a @= b
équivalent à
a = dot(a, b)
où dot
est, par exemple, la fonction de multiplication de matrice numpy et a
et b
sont des matrices.
Je ne sais pas non plus quoi rechercher en tant que recherche Python docs ou Google ne renvoie pas de résultats pertinents lorsque le symbole @ est inclus.
Si vous voulez avoir une vue assez complète de ce que fait un élément particulier de la syntaxe python, regardez directement le fichier de grammaire. Pour la branche 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
Nous pouvons voir ici que @
est utilisé dans trois contextes:
Une recherche google pour "decorator python docs" donne comme résultat principal la section "Instructions composées" de la "Référence du langage Python". En descendant vers le section sur les définitions de fonctions , que nous pouvons trouver en cherchant le mot "décorateur", nous voyons que ... il y a beaucoup à lire. Mais la Parole, "décorateur" est un lien vers le glossaire , qui nous dit:
décorateur
Une fonction renvoyant une autre fonction, généralement appliquée en tant que transformation de fonction à l'aide de la syntaxe
@wrapper
. Les exemples courants pour les décorateurs sontclassmethod()
etstaticmethod()
.La syntaxe du décorateur est simplement du sucre syntaxique, les deux définitions de fonction suivantes sont sémantiquement équivalentes:
def f(...): ... f = staticmethod(f) @staticmethod def f(...): ...
Le même concept existe pour les classes, mais y est moins utilisé. Consultez la documentation pour les définitions de fonction et les définitions de classe pour en savoir plus sur les décorateurs.
Donc, on voit que
@foo
def bar():
pass
est sémantiquement identique à:
def bar():
pass
bar = foo(bar)
Ils ne sont pas exactement les mêmes car Python évalue l'expression foo (qui peut être une recherche en pointillé et un appel de fonction) avant la barre avec le décorateur (syntaxe @
), mais évalue l'expression foo après dans l’autre cas.
(Si cette différence fait une différence dans la signification de votre code, vous devriez reconsidérer ce que vous faites dans votre vie, car ce serait pathologique.)
Si nous revenons à la documentation sur la syntaxe de définition de fonction, nous voyons:
@f1(arg) @f2 def func(): pass
est à peu près équivalent à
def func(): pass func = f1(arg)(f2(func))
Ceci est une démonstration que nous pouvons appeler une fonction qui est d'abord un décorateur, ainsi que des décorateurs empilés. Les fonctions, en Python, sont des objets de première classe, ce qui signifie que vous pouvez transmettre une fonction sous forme d'argument à une autre et renvoyer des fonctions. Les décorateurs font ces deux choses.
Si nous empilons des décorateurs, la fonction, telle que définie, est d'abord transmise au décorateur immédiatement supérieur, puis à la suivante, etc.
Cela résume l’utilisation de @
dans le contexte des décorateurs.
@
Dans la section d'analyse lexicale de la référence de langue, nous avons une section sur les opérateurs , qui inclut @
, ce qui en fait également un opérateur:
Les jetons suivants sont des opérateurs:
+ - * ** / // % @ << >> & | ^ ~ < > <= >= == !=
et dans la page suivante, le modèle de données, nous avons la section Emulation des types numériques ,
object.__add__(self, other) object.__sub__(self, other) object.__mul__(self, other) object.__matmul__(self, other) object.__truediv__(self, other) object.__floordiv__(self, other)
[...] Ces méthodes sont appelées pour implémenter les opérations arithmétiques binaires (
+
,-
,*
,@
,/
,//
, [...]
Et on voit que __matmul__
correspond à @
. Si nous recherchons dans la documentation "matmul", nous obtenons un lien vers Nouveautés de Python 3.5 avec "matmul" sous un en-tête "PEP 465 - Opérateur d’infix dédié pour la multiplication de matrices ".
il peut être implémenté en définissant
__matmul__()
,__rmatmul__()
et__imatmul__()
pour une multiplication de matrice régulière, réfléchie et sur place.
(Nous apprenons donc maintenant que @=
est la version sur place). Il explique plus loin:
La multiplication matricielle est une opération particulièrement courante dans de nombreux domaines des mathématiques, des sciences, de l'ingénierie, et l'ajout de @ permet d'écrire du code plus propre:
S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r)
au lieu de:
S = dot((dot(H, beta) - r).T, dot(inv(dot(dot(H, V), H.T)), dot(H, beta) - r))
Bien que cet opérateur puisse être surchargé pour faire presque n'importe quoi, dans numpy
, par exemple, nous utiliserions cette syntaxe pour calculer le produit intérieur et extérieur des tableaux et des matrices:
>>> 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]])
@=
En recherchant l'utilisation antérieure, nous apprenons qu'il existe également la multiplication de matrice en place. Si nous essayons de l'utiliser, nous découvrirons peut-être qu'il n'est pas encore implémenté pour 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'.
Quand il sera mis en œuvre, je pense que le résultat devrait ressembler à ceci:
>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])
Que fait le symbole “at” (@) en Python?
@ symbol est un sucre syntaxique python permet d'utiliser decorator
,
Pour paraphraser la question, c’est exactement ce que fait le décorateur en Python?
Dit simplement decorator
vous permet de modifier la définition d'une fonction donnée sans toucher à son plus profond (c'est la fermeture).
C’est le cas le plus fréquent lorsque vous importez un merveilleux paquet depuis un tiers. Vous pouvez le visualiser, vous pouvez l'utiliser, mais vous ne pouvez toucher ni à son cœur ni à son cœur.
Voici un exemple rapide,
supposons que je définisse une fonction read_a_book
sur 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: '
Vous voyez, j'ai oublié d'y ajouter un nom.
Comment résoudre un tel problème? Bien sûr, je pourrais redéfinir la fonction comme:
def read_a_book():
return "I am reading the book: 'Python Cookbook'"
Néanmoins, que se passe-t-il si je ne suis pas autorisé à manipuler la fonction d'origine ou s'il en existe des milliers à gérer?.
Résoudre le problème en pensant différemment et définir une nouvelle fonction
def add_a_book(func):
def wrapper():
return func() + "Python Cookbook"
return wrapper
Alors employez-le.
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, tu vois, j'ai amendé read_a_book
sans toucher à la fermeture intérieure. Rien ne m'arrête équipé de decorator
.
Qu'en est-il de @
@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
est une manière élégante et pratique de dire read_a_book = add_a_book(read_a_book)
, c'est un sucre syntaxique, rien de plus sophistiqué à ce sujet.
Si vous faites référence à du code dans un cahier python qui utilise la bibliothèque Numpy, alors @ operator
signifie Multiplication de matrice. Par exemple:
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
À partir de Python 3.5, le symbole '@' est utilisé comme symbole infixe dédié à MATRIX MULTIPLICATION (PEP 0465 - voir https://www.python.org/dev/peps/pep) -0465 / )
@ symbol est également utilisé pour accéder aux variables dans une requête plydata/pandas dataframe, pandas.DataFrame.query
. Exemple:
df = pandas.DataFrame({'foo': [1,2,15,17]})
y = 10
df >> query('foo > @y') # plydata
df.query('foo > @y') # pandas
Les décorateurs ont été ajoutés dans Python pour faciliter la lecture et la compréhension de la fonction et de la méthode (une fonction qui reçoit une fonction et renvoie une fonction améliorée). Le cas d'utilisation initial était de pouvoir définir les méthodes en tant que méthodes de classe ou méthodes statiques en tête de leur définition. Sans la syntaxe de décorateur, cela nécessiterait une définition plutôt rare et répétitive:
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)
Si la syntaxe de décorateur est utilisée dans le même but, le code est plus court et plus facile à comprendre:
class WithDecorators:
@staticmethod
def some_static_method():
print("this is static method")
@classmethod
def some_class_method(cls):
print("this is class method")
Syntaxe générale et implémentations possibles
Le décorateur est généralement un objet nommé ( les expressions lambda ne sont pas autorisées ) qui accepte un seul argument lorsque appelé (ce sera la fonction décorée) et retourne un autre objet appelable. "Appelable" est utilisé ici au lieu de "fonction" avec préméditation. Bien que les décorateurs soient souvent abordés dans le cadre de méthodes et de fonctions, ils ne se limitent pas à eux. En fait, tout ce qui est appelable (tout objet implémentant la méthode _call__ est considéré comme appelable) peut être utilisé comme décorateur et les objets renvoyés par celles-ci ne sont souvent pas de simples fonctions, mais davantage d'instances de classes plus complexes implémentant leur propre méthode __call_.
La syntaxe du décorateur est simplement un sucre syntaxique . Considérez l'utilisation suivante du décorateur:
@some_decorator
def decorated_function():
pass
Cela peut toujours être remplacé par un appel explicite de décorateur et une réaffectation de fonction:
def decorated_function():
pass
decorated_function = some_decorator(decorated_function)
Cependant, ce dernier est moins lisible et très difficile à comprendre si plusieurs décorateurs sont utilisés pour une même fonction. Les décorateurs peuvent être utilisés de différentes manières, comme indiqué ci-dessous:
En tant que fonction
Il existe de nombreuses façons d'écrire des décorateurs personnalisés, mais le moyen le plus simple consiste à écrire une fonction qui renvoie une sous-fonction qui encapsule l'appel de fonction d'origine.
Les modèles génériques sont les suivants:
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
En tant que classe
Bien que les décorateurs puissent presque toujours être implémentés à l'aide de fonctions, il est parfois préférable d'utiliser des classes définies par l'utilisateur. Cela est souvent vrai lorsque le décorateur a besoin d'une paramétrisation complexe ou dépend d'un état spécifique.
Le modèle générique pour un décorateur non paramétré en tant que classe est le suivant:
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
Paramétrage des décorateurs
Dans le vrai code, il est souvent nécessaire d'utiliser des décorateurs paramétrables. Lorsque la fonction est utilisée en tant que décoratrice, la solution est simple: un second niveau d’emballage doit être utilisé. Voici un exemple simple de décorateur qui répète l'exécution d'une fonction décorée le nombre de fois spécifié à chaque appel:
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
Le décorateur défini de cette manière peut accepter les paramètres suivants:
>>> @repeat(2)
... def foo():
... print("foo")
...
>>> foo()
foo
foo
Notez que même si les arguments par défaut du décorateur sont paramétrés, les parenthèses après son nom sont obligatoires. La manière correcte d'utiliser le décorateur précédent avec les arguments par défaut est la suivante:
>>> @repeat()
... def bar():
... print("bar")
...
>>> bar()
bar
bar
bar
Enfin, permet de voir les décorateurs avec Propriétés.
Propriétés
Les propriétés fournissent un type descripteur intégré qui sait comment lier un attribut à un ensemble de méthodes. Une propriété prend quatre arguments facultatifs: fget, fset, fdel et doc. Le dernier peut être fourni pour définir une chaîne de documentation liée à l'attribut comme s'il s'agissait d'une méthode. Voici un exemple de classe Rectangle pouvant être contrôlée soit par accès direct à des attributs stockant deux points d'angle, soit en utilisant les propriétés width et 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 meilleure syntaxe pour la création de propriétés consiste à utiliser property en tant que décorateur. Cela réduira le nombre de signatures de méthode à l’intérieur de la classe et rendra le code plus lisible et maintenable . Avec les décorateurs, la classe ci-dessus devient:
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
Dire ce que les autres ont d'une manière différente: oui, c'est un décorateur.
En Python, c'est comme:
Cela peut être utilisé pour toutes sortes de choses utiles, rendues possibles parce que les fonctions sont des objets et juste des instructions nécessaires.
Cela indique que vous utilisez un décorateur. Voici exemple de Bruce Eckel à partir de 2008.