Que fait le symbole «at» (@) en Python?

580

Je regarde du code Python qui a utilisé le @symbole, mais je n'ai aucune idée de ce qu'il fait. Je ne sais pas non plus quoi rechercher car la recherche de documents Python ou Google ne renvoie pas de résultats pertinents lorsque le @symbole est inclus.

AJ00200
la source

Réponses:

305

Un @symbole au début d'une ligne est utilisé pour les décorateurs de classe, de fonction et de méthode .

En savoir plus ici:

PEP 318: Décorateurs

Décorateurs Python

Les décorateurs Python les plus courants que vous rencontrerez sont:

@propriété

@classmethod

@staticmethod

Si vous voyez un @au milieu d'une ligne, c'est autre chose, la multiplication matricielle. Faites défiler vers le bas pour voir d'autres réponses qui répondent à cette utilisation de @.

FogleBird
la source
31
Il semble qu'il puisse également s'agir d'un opérateur de multiplication matriciel: stackoverflow.com/a/21563036/5049813
Pro Q
@decorators peuvent également être ajoutés
Vijay Panchal
349

Exemple

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 le function/ method/ classque vous définissez après un décorateur est simplement transmis comme un argumentau function/ methodimmédiatement après le @signe.

Première observation

Le flacon microframework présente les décorateurs depuis le tout début dans le format suivant:

from flask import Flask
app = Flask(__name__)

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

Cela se traduit à son tour 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 finalement permis de me sentir en paix avec Flask.

Morgan Wilde
la source
7
Dans le cas de Flasks app.route("/"): cette fonction retourne une fonction, que vous invoquez avec votre hello()comme argument
shaqed
3
Quel est l'avantage syntaxique ou pratique d'avoir des décorateurs ici, au lieu (par exemple) d'appeler simplement quelque chose comme app.route("/", hello)immédiatement après la définition hello, ou même de définir hellocomme lambda dans les arguments de app.route? (Ce dernier exemple est commun avec les http.Serverroutes Node.js et Express.)
iono
186

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 retournés par une fonction normalement.

Matheus Araujo
la source
1
Dans cette ligne s "ome_func = décorateur (some_func)", le premier some_func est une variable = à la fonction some_func, correct?
Viragos
147

Dans Python 3.5, vous pouvez surcharger @en tant qu'opérateur. Il est nommé comme __matmul__, car il est conçu pour effectuer une multiplication matricielle, mais il peut être tout ce que vous voulez. Voir PEP465 pour plus de détails.

Il s'agit d'une implémentation simple 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]]
jinhwanlazy
la source
14
Vous avez également l' @=opérateur (sur place), qui est __imatmul__.
Pål GD
Existe-t-il d'autres opérateurs remplaçables comme celui-ci? Je connais __add__et __sub__sont liés à + et - respectivement, mais je n'ai jamais entendu parler du @signe auparavant. Y en a-t-il d'autres qui se cachent là-bas?
Thomas Kimber
103

Que fait le symbole «at» (@) en Python?

En bref, il est utilisé dans la syntaxe du décorateur et pour la multiplication matricielle.

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 matricielle, a @ binvoque a.__matmul__(b)- ce qui rend cette syntaxe:

a @ b

équivalent à

dot(a, b)

et

a @= b

équivalent à

a = dot(a, b)

dotest, par exemple, la fonction de multiplication matricielle numpy et aet bsont des matrices.

Comment pourriez-vous découvrir cela par vous-même?

Je ne sais pas non plus quoi rechercher car la recherche de documents Python 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 morceau particulier de 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 qu'il @est utilisé dans trois contextes:

  • décorateurs
  • un opérateur entre les facteurs
  • un opérateur d'affectation augmenté

Syntaxe du décorateur:

Une recherche google pour "docs python décorateur" donne comme l'un des meilleurs résultats, la section "Instructions composées" de la "Référence du langage Python". En descendant jusqu'à la section sur les définitions des fonctions , que nous pouvons trouver en recherchant le mot "décorateur", nous voyons que ... il y a beaucoup à lire. Mais le mot "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 @wrappersyntaxe. Les exemples courants pour les décorateurs sont classmethod()et staticmethod().

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 pourrait être une recherche en pointillés et un appel de fonction) avant bar avec la @syntaxe decorator ( ), mais évalue l'expression foo après bar 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 de votre vie, car ce serait pathologique.)

Décorateurs empilés

Si nous revenons à la documentation de 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 de pile. Les fonctions, en Python, sont des objets de première classe - ce qui signifie que vous pouvez passer une fonction comme argument à une autre fonction et renvoyer des fonctions. Les décorateurs font ces deux choses.

Si nous empilons les décorateurs, la fonction, telle que définie, est transmise en premier au décorateur immédiatement au-dessus, puis au suivant, et ainsi de suite.

Cela résume l'utilisation @dans le contexte des décorateurs.

L'opérateur, @

Dans la section d'analyse lexicale de la référence du langage, 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 Émulation 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 à mettre en œuvre les opérations arithmétiques binaires ( +, -, *, @, /, //, [...]

Et nous voyons que cela __matmul__correspond à @. Si nous recherchons la documentation pour "matmul", nous obtenons un lien vers Quoi de neuf dans Python 3.5 avec "matmul" sous un titre "PEP 465 - Un opérateur d'infixe dédié pour la multiplication matricielle".

il peut être implémenté en définissant __matmul__(), __rmatmul__()et __imatmul__()pour une multiplication matricielle régulière, réfléchie et sur place.

(Alors maintenant, nous apprenons que @=c'est la version sur place). Il explique en outre:

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

Multiplication de matrice inplace: @=

En recherchant l'utilisation antérieure, nous apprenons qu'il y a aussi la multiplication matricielle interne. Si nous essayons de l'utiliser, nous pouvons constater 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 implémenté, je m'attendrais à ce que le résultat ressemble à ceci:

>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])
Aaron Hall
la source
36

Que fait le symbole «at» (@) en Python?

@ symbol est un sucre syntaxique que python fournit pour utiliser decorator,
pour paraphraser la question, c'est exactement ce que fait le décorateur en Python?

En termes simples, decoratorvous pouvez modifier la définition d'une fonction donnée sans toucher son plus profond (c'est la fermeture).
C'est le cas le plus important lorsque vous importez un merveilleux paquet de tiers. Vous pouvez le visualiser, vous pouvez l'utiliser, mais vous ne pouvez pas toucher son intérieur et son cœur.

Voici un exemple rapide,
supposons que je définisse une read_a_bookfonction 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 y a des milliers de fonctions à 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

Ensuite, 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, vous voyez, j'ai modifié read_a_booksans y toucher la fermeture intérieure. Rien ne m'arrête équipé decorator.

De quoi s'agit-il @

@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_bookest une façon élégante et pratique de dire read_a_book = add_a_book(read_a_book), c'est un sucre syntaxique, il n'y a rien de plus sophistiqué.

Calcul
la source
16

Si vous faites référence à du code dans un bloc-notes python qui utilise la bibliothèque Numpy , cela @ operatorsignifie la multiplication matricielle . 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
f__society
la source
6

Des décorateurs ont été ajoutés en Python pour faciliter l' habillage des fonctions et des méthodes (une fonction qui reçoit une fonction et en renvoie une améliorée). Le cas d'utilisation d'origine é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 du décorateur, il faudrait une définition plutôt clairsemée 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 du 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 lorsqu'il est appelé (ce sera la fonction décorée) et renvoie 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 des méthodes et des fonctions, ils ne se limitent pas à eux. En fait, tout ce qui est appelable (tout objet qui implémente la méthode _call__ est considéré comme appelable), peut être utilisé comme décorateur et souvent les objets retournés par eux ne sont pas de simples fonctions mais davantage d'instances de classes plus complexes implémentant leur propre méthode __call_.

La syntaxe du décorateur n'est tout simplement qu'un sucre syntaxique . Considérez l'utilisation du décorateur suivant:

@some_decorator
def decorated_function():
    pass

Cela peut toujours être remplacé par un appel de décorateur explicite et une réaffectation de fonction:

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

Cependant, ce dernier est moins lisible et aussi très difficile à comprendre si plusieurs décorateurs sont utilisés sur une seule 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 la manière la 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

Alors que les décorateurs peuvent presque toujours être implémentés à l'aide de fonctions, il existe certaines situations où l'utilisation de classes définies par l'utilisateur est une meilleure option. Cela est souvent vrai lorsque le décorateur a besoin d'un paramétrage complexe ou cela 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étrer les décorateurs

Dans le code réel, il est souvent nécessaire d'utiliser des décorateurs qui peuvent être paramétrés. Lorsque la fonction est utilisée comme décorateur, la solution est simple: un deuxième niveau d'emballage doit être utilisé. Voici un exemple simple du décorateur qui répète l'exécution d'une fonction décorée le nombre de fois spécifié chaque fois qu'elle est appelée:

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 ainsi défini peut accepter des paramètres:

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

Notez que même si le décorateur paramétré a des valeurs par défaut pour ses arguments, les parenthèses après son nom sont obligatoires. La façon correcte d'utiliser le décorateur précédent avec des arguments par défaut est la suivante:

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

Permet enfin de voir les décorateurs avec des propriétés.

Propriétés

Les propriétés fournissent un type de 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 docstring qui est liée à l'attribut comme s'il s'agissait d'une méthode. Voici un exemple de classe Rectangle qui peut être contrôlée soit par un accès direct aux attributs qui stockent 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 créer des propriétés est d'utiliser la propriété comme 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
iun1x
la source
2

Dire ce que les autres ont d'une manière différente: oui, c'est un décorateur.

En Python, c'est comme:

  1. Création d'une fonction (suit sous l'appel @)
  2. Appel d'une autre fonction pour opérer sur votre fonction créée. Cela renvoie une nouvelle fonction. La fonction que vous appelez est l'argument du @.
  3. Remplacement de la fonction définie par la nouvelle fonction renvoyée.

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.

Mayur Patel
la source
2

@ Symbole est également utilisé pour les variables d'accès à l' intérieur d' un plydata / pandas géants dataframe requête, pandas.DataFrame.query. Exemple:

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

Cela indique que vous utilisez un décorateur. Voici l'exemple de Bruce Eckel de 2008.

Peter Rowell
la source