¿Qué hace el símbolo "at" (@) en Python?

580

Estoy viendo un código de Python que usó el @símbolo, pero no tengo idea de lo que hace. Tampoco sé qué buscar, ya que buscar documentos de Python o Google no devuelve resultados relevantes cuando @se incluye el símbolo.

AJ00200
fuente

Respuestas:

305

Un @símbolo al comienzo de una línea se usa para decoradores de clase, función y método .

Leer más aquí:

PEP 318: Decoradores

Decoradores Python

Los decoradores de Python más comunes con los que te encontrarás son:

@propiedad

@classmethod

@staticmethod

Si ve un @en el medio de una línea, eso es algo diferente, la multiplicación de matrices. Desplácese hacia abajo para ver otras respuestas que aborden ese uso @.

FogleBird
fuente
31
Parece que también puede ser un operador de multiplicación de matrices: stackoverflow.com/a/21563036/5049813
Pro Q
También se pueden agregar @ decoradores
Vijay Panchal
349

Ejemplo

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

Esto muestra que el function/ method/ classque estás definiendo después de un decorador se pasa básicamente como / argumental function/ methodinmediatamente después del @signo.

Primer avistamiento

El matraz de microframework introduce decoradores desde el principio en el siguiente formato:

from flask import Flask
app = Flask(__name__)

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

Esto a su vez se traduce en:

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

Darme cuenta de esto finalmente me permitió sentirme en paz con Flask.

Morgan Wilde
fuente
77
En el caso de Flasks app.route("/"): esta función devuelve una función, la cual hello()
invocas
3
¿Cuál es el beneficio sintáctico o práctico de tener decoradores aquí, en lugar de (por ejemplo) simplemente llamar a algo como app.route("/", hello)inmediatamente después de definir hello, o incluso definir hellocomo lambda en los argumentos app.route? (El último ejemplo es común con las http.Serverrutas Node.js y Express.)
iono
186

Este fragmento de código:

def decorator(func):
   return func

@decorator
def some_func():
    pass

Es equivalente a este código:

def decorator(func):
    return func

def some_func():
    pass

some_func = decorator(some_func)

En la definición de un decorador, puede agregar algunas cosas modificadas que una función no devolvería normalmente.

Matheus Araujo
fuente
1
En esta línea s "ome_func = decorator (some_func)", el primer some_func es una variable = de la función some_func, ¿correcto?
Viragos
147

En Python 3.5 puede sobrecargarse @como operador. Se llama así __matmul__porque está diseñado para multiplicar matrices, pero puede ser lo que quieras. Ver PEP465 para más detalles.

Esta es una implementación simple de la multiplicación de matrices.

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)

Este código produce:

[[18, 14], [62, 66]]
Jinhwanlazy
fuente
14
También tiene el @=operador (en el lugar), que es __imatmul__.
Pål GD
¿Hay otros operadores reemplazables como este? Sé __add__y estoy __sub__vinculado a + y - respectivamente, pero nunca antes había oído hablar del @signo. ¿Hay otros al acecho por ahí?
Thomas Kimber
103

¿Qué hace el símbolo "at" (@) en Python?

En resumen, se usa en la sintaxis del decorador y para la multiplicación de matrices.

En el contexto de los decoradores, esta sintaxis:

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

es equivalente a esto:

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

decorated_function = decorator(decorated_function)

En el contexto de la multiplicación de matrices, a @ binvoca a.__matmul__(b), haciendo esta sintaxis:

a @ b

equivalente a

dot(a, b)

y

a @= b

equivalente a

a = dot(a, b)

donde dotes, por ejemplo, la función de multiplicación de matrices numpy y ay bson matrices.

¿Cómo puedes descubrir esto por tu cuenta?

Tampoco sé qué buscar, ya que buscar documentos de Python o Google no devuelve resultados relevantes cuando se incluye el símbolo @.

Si desea tener una vista bastante completa de lo que hace una parte de la sintaxis de Python en particular, mire directamente el archivo de gramática. Para la rama 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

Podemos ver aquí que @se usa en tres contextos:

  • decoradores
  • un operador entre factores
  • un operador de asignación aumentada

Sintaxis del decorador:

Una búsqueda en Google de "decorator python docs" da como uno de los mejores resultados, la sección "Declaraciones compuestas" de la "Referencia del lenguaje Python". Desplazándonos hacia la sección de definiciones de funciones , que podemos encontrar buscando la palabra "decorador", vemos que ... hay mucho para leer. Pero la palabra "decorador" es un enlace al glosario , que nos dice:

decorador

Una función que devuelve otra función, generalmente aplicada como una transformación de función utilizando la @wrappersintaxis. Ejemplos comunes para decoradores son classmethod()y staticmethod().

La sintaxis del decorador es simplemente azúcar sintáctica, las siguientes dos definiciones de funciones son semánticamente equivalentes:

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

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

El mismo concepto existe para las clases, pero se usa con menos frecuencia allí. Consulte la documentación para las definiciones de funciones y definiciones de clases para obtener más información sobre decoradores.

Entonces, vemos que

@foo
def bar():
    pass

es semánticamente lo mismo que:

def bar():
    pass

bar = foo(bar)

No son exactamente iguales porque Python evalúa la expresión foo (que podría ser una búsqueda punteada y una llamada a función) antes de la barra con la @sintaxis decorator ( ), pero evalúa la expresión foo después de la barra en el otro caso.

(Si esta diferencia hace una diferencia en el significado de su código, debe reconsiderar lo que está haciendo con su vida, porque eso sería patológico).

Decoradores apilados

Si volvemos a la documentación de sintaxis de definición de función, vemos:

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

es más o menos equivalente a

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

Esta es una demostración de que podemos llamar a una función que es primero un decorador, así como a los decoradores de pila. Las funciones, en Python, son objetos de primera clase, lo que significa que puede pasar una función como argumento a otra función y devolver funciones. Los decoradores hacen ambas cosas.

Si apilamos los decoradores, la función, tal como se define, se pasa primero al decorador inmediatamente por encima, luego al siguiente, y así sucesivamente.

Eso resume el uso @en el contexto de los decoradores.

El operador, @

En la sección de análisis léxico de la referencia del lenguaje, tenemos una sección sobre operadores , que incluye @, que también la convierte en un operador:

Los siguientes tokens son operadores:

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

y en la página siguiente, el Modelo de datos, tenemos la sección Emulación de tipos numéricos ,

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

[...] Estos métodos son llamados a poner en práctica las operaciones aritméticas binarias ( +, -, *, @, /, //, [...]

Y vemos que __matmul__corresponde a @. Si buscamos en la documentación "matmul", obtenemos un enlace a Novedades en Python 3.5 con "matmul" bajo el título "PEP 465 - Un operador infijo dedicado para la multiplicación de matrices".

se puede implementar definiendo __matmul__(), __rmatmul__()y __imatmul__()para multiplicación de matriz regular, reflejada y en el lugar.

(Así que ahora aprendemos que @=es la versión in situ). Explica además:

La multiplicación de matrices es una operación notablemente común en muchos campos de las matemáticas, la ciencia, la ingeniería, y la adición de @ permite escribir código más limpio:

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

en vez de:

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

Si bien este operador se puede sobrecargar para hacer casi cualquier cosa, en numpy, por ejemplo, usaríamos esta sintaxis para calcular el producto interno y externo de matrices y 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]])

Multiplicación de matriz in situ: @=

Mientras investigamos el uso anterior, aprendemos que también existe la multiplicación de matriz in situ. Si intentamos usarlo, podemos encontrar que aún no está implementado para 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'.

Cuando se implemente, esperaría que el resultado se vea así:

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

¿Qué hace el símbolo "at" (@) en Python?

El símbolo @ es un azúcar sintáctico que Python proporciona para utilizar decorator,
parafraseando la pregunta, ¿Se trata exactamente de qué hace el decorador en Python?

En pocas palabras, le decoratorpermite modificar la definición de una función determinada sin tocar su parte más interna (su cierre).
Es el caso más importante cuando importa un paquete maravilloso de un tercero. Puedes visualizarlo, puedes usarlo, pero no puedes tocar su interior y su corazón.

Aquí hay un ejemplo rápido,
supongamos que defino una read_a_bookfunción en 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: '

Verás, olvidé agregarle un nombre.
¿Cómo resolver tal problema? Por supuesto, podría redefinir la función como:

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

Sin embargo, ¿qué pasa si no se me permite manipular la función original, o si hay miles de tales funciones para manejar?

Resuelva el problema pensando diferente y defina una nueva_función

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

Entonces úsalo.

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, ya ves, modifiqué read_a_booksin tocarlo cierre interno. Nada me detiene equipado con decorator.

De que se trata @

@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_bookes una manera elegante y práctica de decir read_a_book = add_a_book(read_a_book), es un azúcar sintáctico, no hay nada más elegante al respecto.

Cálculo
fuente
16

Si se está refiriendo a algún código en un cuaderno de Python que está usando la biblioteca Numpy , entonces @ operatorsignifica Multiplicación matricial . Por ejemplo:

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
fuente
6

Se agregaron decoradores en Python para que el ajuste de funciones y métodos (una función que recibe una función y devuelve una mejorada) sea más fácil de leer y comprender. El caso de uso original era poder definir los métodos como métodos de clase o métodos estáticos en la cabeza de su definición. Sin la sintaxis del decorador, requeriría una definición bastante escasa y repetitiva:

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 sintaxis del decorador se usa para el mismo propósito, el código es más corto y más fácil de entender:

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

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

Sintaxis general y posibles implementaciones

El decorador es generalmente un objeto con nombre ( no se permiten expresiones lambda ) que acepta un solo argumento cuando se lo llama (será la función decorada) y devuelve otro objeto invocable. "Callable" se usa aquí en lugar de "función" con premeditación. Si bien los decoradores a menudo se discuten en el alcance de los métodos y funciones, no se limitan a ellos. De hecho, cualquier cosa que sea invocable (cualquier objeto que implemente el método _call__ se considera invocable), puede usarse como decorador y, a menudo, los objetos devueltos por ellos no son funciones simples sino más instancias de clases más complejas que implementan su propio método __call_.

La sintaxis decoradora es simplemente un azúcar sintáctico . Considere el siguiente uso de decorador:

@some_decorator
def decorated_function():
    pass

Esto siempre se puede reemplazar por una llamada explícita del decorador y la reasignación de funciones:

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

Sin embargo, este último es menos legible y también muy difícil de entender si se utilizan múltiples decoradores en una sola función. Los decoradores se pueden usar de varias maneras diferentes como se muestra a continuación:

Como una función

Hay muchas formas de escribir decoradores personalizados, pero la forma más simple es escribir una función que devuelva una subfunción que envuelva la llamada a la función original.

Los patrones genéricos son los siguientes:

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

Como una clase

Si bien los decoradores casi siempre se pueden implementar usando funciones, hay algunas situaciones en las que usar clases definidas por el usuario es una mejor opción. Esto suele ser cierto cuando el decorador necesita una parametrización compleja o depende de un estado específico.

El patrón genérico para un decorador no parametrizado como clase es el siguiente:

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

Decoradores paramétricos

En el código real, a menudo es necesario usar decoradores que se puedan parametrizar. Cuando la función se usa como decorador, la solución es simple: se debe usar un segundo nivel de envoltura. Aquí hay un ejemplo simple del decorador que repite la ejecución de una función decorada el número especificado de veces cada vez que se llama:

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

El decorador definido de esta manera puede aceptar parámetros:

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

Tenga en cuenta que incluso si el decorador parametrizado tiene valores predeterminados para sus argumentos, los paréntesis después de su nombre son obligatorios. La forma correcta de usar el decorador anterior con argumentos predeterminados es la siguiente:

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

Finalmente veamos decoradores con Propiedades.

Propiedades

Las propiedades proporcionan un tipo de descriptor incorporado que sabe cómo vincular un atributo a un conjunto de métodos. Una propiedad toma cuatro argumentos opcionales: fget, fset, fdel y doc. El último se puede proporcionar para definir una cadena de documentos que está vinculada al atributo como si fuera un método. Aquí hay un ejemplo de una clase Rectángulo que se puede controlar mediante el acceso directo a los atributos que almacenan dos puntos de esquina o mediante el uso de las propiedades de ancho y alto:

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 mejor sintaxis para crear propiedades es usar la propiedad como decorador. Esto reducirá la cantidad de firmas de métodos dentro de la clase y hará que el código sea más legible y mantenible . Con decoradores, la clase anterior se convierte en:

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
fuente
2

Para decir lo que otros tienen de una manera diferente: sí, es un decorador.

En Python, es como:

  1. Crear una función (sigue debajo de la llamada @)
  2. Llamar a otra función para operar en su función creada. Esto devuelve una nueva función. La función que llamas es el argumento de la @.
  3. Reemplazar la función definida con la nueva función devuelta.

Esto se puede usar para todo tipo de cosas útiles, hecho posible porque las funciones son objetos y solo son necesarias instrucciones.

Mayur Patel
fuente
2

@ Símbolo también se utiliza para las variables de acceso dentro de un plydata / pandas trama de datos de consulta, pandas.DataFrame.query. Ejemplo:

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