Respuestas:
Un @
símbolo al comienzo de una línea se usa para decoradores de clase, función y método .
Leer más aquí:
Los decoradores de Python más comunes con los que te encontrarás son:
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 @
.
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
/ class
que estás definiendo después de un decorador se pasa básicamente como / argument
al function
/ method
inmediatamente después del @
signo.
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.
app.route("/")
: esta función devuelve una función, la cual hello()
app.route("/", hello)
inmediatamente después de definir hello
, o incluso definir hello
como lambda en los argumentos app.route
? (El último ejemplo es común con las http.Server
rutas Node.js y Express.)
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.
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]]
@=
operador (en el lugar), que es __imatmul__
.
__add__
y estoy __sub__
vinculado a + y - respectivamente, pero nunca antes había oído hablar del @
signo. ¿Hay otros al acecho por ahí?
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 @ b
invoca a.__matmul__(b)
, haciendo esta sintaxis:
a @ b
equivalente a
dot(a, b)
y
a @= b
equivalente a
a = dot(a, b)
donde dot
es, por ejemplo, la función de multiplicación de matrices numpy y a
y b
son matrices.
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:
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
@wrapper
sintaxis. Ejemplos comunes para decoradores sonclassmethod()
ystaticmethod()
.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).
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.
@
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]])
@=
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]])
¿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 decorator
permite 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_book
funció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_book
sin 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_book
es 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.
Si se está refiriendo a algún código en un cuaderno de Python que está usando la biblioteca Numpy , entonces @ operator
significa 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
Comenzando con Python 3.5, la '@' se usa como un símbolo infijo dedicado para MULTIPLICACIÓN DE MATRIZ (PEP 0465 - ver https://www.python.org/dev/peps/pep-0465/ )
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
Para decir lo que otros tienen de una manera diferente: sí, es un decorador.
En Python, es como:
Esto se puede usar para todo tipo de cosas útiles, hecho posible porque las funciones son objetos y solo son necesarias instrucciones.
Indica que está utilizando un decorador. Aquí está el ejemplo de Bruce Eckel de 2008.