Respostas:
Um @
símbolo no início de uma linha é usado para decoradores de classe, função e método .
Leia mais aqui:
Os decoradores Python mais comuns nos quais você encontrará são:
Se você vê um @
no meio de uma linha, isso é algo diferente, multiplicação de matrizes. Role para baixo para ver outras respostas que abordam esse uso de @
.
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']
Isso mostra que o function
/ method
/ class
Você está definindo depois de um decorador é apenas basicamente repassados como uma argument
ao function
/ method
imediatamente após o @
sinal.
O Flask de microframework apresenta decoradores desde o início no seguinte formato:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def hello():
return "Hello World!"
Por sua vez, isso se traduz em:
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
Perceber isso finalmente me permitiu sentir-me em paz com o Flask.
app.route("/")
: esta função retorna uma função, que você invocar com o seu hello()
como um argumento
app.route("/", hello)
imediatamente após definir hello
, ou mesmo definir hello
como um lambda nos argumentos para app.route
? (O último exemplo é comum com as http.Server
rotas Node.js e Express.)
Este trecho de código:
def decorator(func):
return func
@decorator
def some_func():
pass
É equivalente a este código:
def decorator(func):
return func
def some_func():
pass
some_func = decorator(some_func)
Na definição de um decorador, você pode adicionar algumas coisas modificadas que não seriam retornadas por uma função normalmente.
No Python 3.5, você pode sobrecarregar @
como operador. É nomeado como __matmul__
, porque foi projetado para fazer a multiplicação de matrizes, mas pode ser o que você quiser. Veja PEP465 para detalhes.
Esta é uma implementação simples da multiplicação de matrizes.
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 produz:
[[18, 14], [62, 66]]
@=
operador (no local), que é __imatmul__
.
__add__
e estou __sub__
vinculado a + e - respectivamente, mas nunca ouvi falar do @
sinal antes. Existem outros por aí?
Em resumo, é usado na sintaxe do decorador e na multiplicação de matrizes.
No contexto dos decoradores, esta sintaxe:
@decorator
def decorated_function():
"""this function is decorated"""
é equivalente a isso:
def decorated_function():
"""this function is decorated"""
decorated_function = decorator(decorated_function)
No contexto da multiplicação de matrizes, a @ b
chama a.__matmul__(b)
- fazendo esta sintaxe:
a @ b
equivalente a
dot(a, b)
e
a @= b
equivalente a
a = dot(a, b)
onde dot
é, por exemplo, a função de multiplicação da matriz numpy e a
e b
são matrizes.
Também não sei o que procurar ao pesquisar documentos do Python ou o Google não retorna resultados relevantes quando o símbolo @ é incluído.
Se você deseja ter uma visão bastante completa do que uma parte específica da sintaxe python faz, consulte diretamente o arquivo de gramática. Para o 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
Podemos ver aqui que @
é usado em três contextos:
Uma pesquisa no google por "decorator python docs" fornece como um dos principais resultados, a seção "Compound Statements" da "Python Language Reference". Rolar para baixo até a seção sobre definições de funções , que podemos encontrar pesquisando a palavra "decorador", vemos que ... há muito o que ler. Mas a palavra "decorador" é um link para o glossário , que nos diz:
decorador
Uma função retornando outra função, geralmente aplicada como uma transformação de função usando a
@wrapper
sintaxe. Exemplos comuns para decoradores sãoclassmethod()
estaticmethod()
.A sintaxe do decorador é apenas um açúcar sintático, as duas definições de função a seguir são semanticamente equivalentes:
def f(...): ... f = staticmethod(f) @staticmethod def f(...): ...
O mesmo conceito existe para as classes, mas é menos usado aqui. Consulte a documentação para obter definições de função e definições de classe para obter mais informações sobre decoradores.
Então, nós vemos isso
@foo
def bar():
pass
é semanticamente o mesmo que:
def bar():
pass
bar = foo(bar)
Eles não são exatamente iguais porque o Python avalia a expressão foo (que pode ser uma pesquisa pontilhada e uma chamada de função) antes da barra com a @
sintaxe decorator ( ), mas avalia a expressão foo após a barra no outro caso.
(Se essa diferença faz diferença no significado do seu código, você deve reconsiderar o que está fazendo da sua vida, porque isso seria patológico.)
Se voltarmos à documentação da sintaxe de definição de função, veremos:
@f1(arg) @f2 def func(): pass
é aproximadamente equivalente a
def func(): pass func = f1(arg)(f2(func))
Esta é uma demonstração que podemos chamar de uma função que é decoradora primeiro, assim como decoradores de pilha. Funções, em Python, são objetos de primeira classe - o que significa que você pode passar uma função como argumento para outra função e retornar funções. Decoradores fazem as duas coisas.
Se empilharmos decoradores, a função, conforme definida, é passada primeiro para o decorador imediatamente acima dela, depois a próxima e assim por diante.
Isso resume o uso @
no contexto dos decoradores.
@
Na seção de análise lexical da referência de idioma, temos uma seção sobre operadores , que inclui @
, o que o torna também um operador:
Os seguintes tokens são operadores:
+ - * ** / // % @ << >> & | ^ ~ < > <= >= == !=
e na próxima página, o Modelo de Dados, temos a seção Emulando 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)
[...] Esses métodos são chamados para implementar as operações aritméticas binárias (
+
,-
,*
,@
,/
,//
, [...]
E vemos que isso __matmul__
corresponde a @
. Se procurarmos na documentação por "matmul", obtemos um link para O que há de novo no Python 3.5 com "matmul" sob o cabeçalho "PEP 465 - Um operador de infix dedicado para multiplicação de matrizes".
ele pode ser implementado através da definição
__matmul__()
,__rmatmul__()
e__imatmul__()
para a multiplicação matriz regular, refletido, e no local.
(Então agora aprendemos que @=
é a versão local). Explica ainda:
A multiplicação de matrizes é uma operação notavelmente comum em muitos campos da matemática, ciências, engenharia e a adição de @ permite escrever código mais limpo:
S = (H @ beta - r).T @ inv(H @ V @ H.T) @ (H @ beta - r)
ao invés de:
S = dot((dot(H, beta) - r).T, dot(inv(dot(dot(H, V), H.T)), dot(H, beta) - r))
Embora esse operador possa estar sobrecarregado para fazer quase qualquer coisa, numpy
por exemplo, usaríamos essa sintaxe para calcular o produto interno e externo de matrizes e matrizes:
>>> 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]])
@=
Ao pesquisar o uso anterior, aprendemos que também há a multiplicação de matrizes no local. Se tentarmos usá-lo, podemos descobrir que ainda não 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'.
Quando é implementado, eu esperaria que o resultado fosse assim:
>>> m = matrix([1,2,3])
>>> m @= m.T
>>> m
matrix([[14]])
O que o símbolo "at" (@) faz no Python?
@ symbol é um açúcar sintático que o python fornece para utilizar decorator
,
parafraseando a pergunta: é exatamente sobre o que o decorador faz no Python?
Simplificando, decorator
você pode modificar a definição de uma determinada função sem tocar na parte mais interna (é o fechamento).
É o caso mais importante quando você importa um pacote maravilhoso de terceiros. Você pode visualizá-lo, pode usá-lo, mas não pode tocar seu interior e seu coração.
Aqui está um exemplo rápido,
suponha que eu defina uma read_a_book
função no 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: '
Você vê, eu esqueci de adicionar um nome a ele.
Como resolver esse problema? Obviamente, eu poderia redefinir a função como:
def read_a_book():
return "I am reading the book: 'Python Cookbook'"
No entanto, e se eu não tiver permissão para manipular a função original ou se houver milhares dessa função a ser manipulada.
Resolva o problema pensando diferente e defina uma nova função
def add_a_book(func):
def wrapper():
return func() + "Python Cookbook"
return wrapper
Então empregue.
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, você vê, eu alterei read_a_book
sem tocar no fechamento interno. Nada me deixa equipado decorator
.
Sobre o quê @
@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
é uma maneira elegante e prática de dizer read_a_book = add_a_book(read_a_book)
, é um açúcar sintático, não há nada mais sofisticado nisso.
Se você está se referindo a algum código em um notebook python que está usando a biblioteca Numpy , @ operator
significa Multiplicação de matriz . Por exemplo:
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
Começando com o Python 3.5, o '@' é usado como um símbolo de infixo dedicado para MATRIX MULTIPLICATION (PEP 0465 - consulte https://www.python.org/dev/peps/pep-0465/ )
Decoradores foram adicionados ao Python para facilitar a leitura e o entendimento da função e do método (uma função que recebe uma função e retorna uma aprimorada). O caso de uso original deveria ser capaz de definir os métodos como métodos de classe ou métodos estáticos no início de sua definição. Sem a sintaxe do decorador, seria necessária uma definição esparsa e 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)
Se a sintaxe do decorador for usada para a mesma finalidade, o código será mais curto e 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")
Sintaxe geral e possíveis implementações
O decorador geralmente é um objeto nomeado ( expressões lambda não são permitidas ) que aceita um único argumento quando chamado (será a função decorada) e retorna outro objeto que pode ser chamado. "Callable" é usado aqui em vez de "function" com premeditação. Embora os decoradores sejam frequentemente discutidos no escopo de métodos e funções, eles não se limitam a eles. De fato, qualquer coisa que possa ser chamada (qualquer objeto que implemente o método _call__ é considerado que pode ser chamado), pode ser usado como um decorador e, freqüentemente, os objetos retornados por elas não são funções simples, mas mais instâncias de classes mais complexas implementando seu próprio método __call_.
A sintaxe do decorador é simplesmente apenas um açúcar sintático . Considere o seguinte uso do decorador:
@some_decorator
def decorated_function():
pass
Isso sempre pode ser substituído por uma chamada explícita do decorador e a reatribuição de função:
def decorated_function():
pass
decorated_function = some_decorator(decorated_function)
No entanto, o último é menos legível e também muito difícil de entender se vários decoradores forem usados em uma única função. Os decoradores podem ser usados de várias maneiras diferentes, como mostrado abaixo:
Como uma função
Existem várias maneiras de escrever decoradores personalizados, mas a maneira mais simples é escrever uma função que retorne uma subfunção que encerre a chamada de função original.
Os padrões genéricos são os seguintes:
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 uma classe
Embora os decoradores quase sempre possam ser implementados usando funções, há algumas situações em que usar classes definidas pelo usuário é uma opção melhor. Isso geralmente acontece quando o decorador precisa de parametrização complexa ou depende de um estado específico.
O padrão genérico para um decorador não parametrizado como uma classe é o seguinte:
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
Parametrizar decoradores
No código real, geralmente é necessário usar decoradores que podem ser parametrizados. Quando a função é usada como decorador, a solução é simples - um segundo nível de embalagem precisa ser usado. Aqui está um exemplo simples do decorador que repete a execução de uma função decorada o número especificado de vezes toda vez que é chamada:
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
O decorador definido desta maneira pode aceitar parâmetros:
>>> @repeat(2)
... def foo():
... print("foo")
...
>>> foo()
foo
foo
Observe que, mesmo que o decorador parametrizado possua valores padrão para seus argumentos, os parênteses após o nome serão obrigatórios. A maneira correta de usar o decorador anterior com argumentos padrão é a seguinte:
>>> @repeat()
... def bar():
... print("bar")
...
>>> bar()
bar
bar
bar
Por fim, vamos ver decoradores com Propriedades.
Propriedades
As propriedades fornecem um tipo de descritor interno que sabe como vincular um atributo a um conjunto de métodos. Uma propriedade recebe quatro argumentos opcionais: fget, fset, fdel e doc. O último pode ser fornecido para definir uma sequência de documentos vinculada ao atributo como se fosse um método. Aqui está um exemplo de uma classe Rectangle que pode ser controlada pelo acesso direto a atributos que armazenam dois pontos de canto ou usando as propriedades 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
)
A melhor sintaxe para criar propriedades é usar property como um decorador. Isso reduzirá o número de assinaturas de método dentro da classe e tornará o código mais legível e de manutenção . Com os decoradores, a classe acima se torna:
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 dizer o que os outros têm de uma maneira diferente: sim, é um decorador.
No Python, é como:
Isso pode ser usado para todos os tipos de coisas úteis, possíveis porque as funções são objetos e são necessárias apenas instruções.
Indica que você está usando um decorador. Aqui está o exemplo de Bruce Eckel de 2008.