O que o símbolo "at" (@) faz no Python?


580

Estou vendo algum código Python que usou o @símbolo, mas não tenho idéia do que ele faz. 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.

Respostas:


305

Um @símbolo no início de uma linha é usado para decoradores de classe, função e método .

Leia mais aqui:

PEP 318: Decoradores

Decoradores Python

Os decoradores Python mais comuns nos quais você encontrará são:

@propriedade

@classmethod

@staticmethod

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 @.


31
Parece que também pode ser um operador de multiplicação de matrizes: stackoverflow.com/a/21563036/5049813
Pro Q

@decorators também podem ser adicionados
Vijay Panchal

349

Exemplo

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/ classVocê está definindo depois de um decorador é apenas basicamente repassados como uma argumentao function/ methodimediatamente após o @sinal.

Primeira vista

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.


7
No caso dos frascos app.route("/"): esta função retorna uma função, que você invocar com o seu hello()como um argumento
shaqed

3
Qual é o benefício sintático ou prático de ter decoradores aqui, em vez de (por exemplo) chamar algo como app.route("/", hello)imediatamente após definir hello, ou mesmo definir hellocomo um lambda nos argumentos para app.route? (O último exemplo é comum com as http.Serverrotas Node.js e Express.)
iono

186

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.


1
Nesta linha s "ome_func = decorador (some_func)", o primeiro some_func é uma variável = para a função some_func, correto?
Viragos 07/07/19

147

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

14
Você também tem o @=operador (no local), que é __imatmul__.
GD

Existem outros operadores substituíveis como este? Eu sei __add__e estou __sub__vinculado a + e - respectivamente, mas nunca ouvi falar do @sinal antes. Existem outros por aí?
Thomas Kimber

103

O que o símbolo "at" (@) faz no Python?

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 @ bchama 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 ae bsão matrizes.

Como você pôde descobrir isso sozinho?

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:

  • decoradores
  • um operador entre fatores
  • um operador de atribuição aumentada

Sintaxe do decorador:

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 @wrappersintaxe. Exemplos comuns para decoradores são classmethod()e staticmethod().

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

Decoradores empilhados

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.

O operador, @

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

Multiplicação de matriz no local: @=

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

36

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, decoratorvocê 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_bookfunçã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_booksem 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.


16

Se você está se referindo a algum código em um notebook python que está usando a biblioteca Numpy , @ operatorsignifica 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


6

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

2

Para dizer o que os outros têm de uma maneira diferente: sim, é um decorador.

No Python, é como:

  1. Criando uma função (segue sob a chamada @)
  2. Chamando outra função para operar em sua função criada. Isso retorna uma nova função. A função que você chama é o argumento do @.
  3. Substituindo a função definida pela nova função retornada.

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.


2

@ Símbolo também é usado para variáveis de acesso dentro de um plydata / pandas trama de dados de consulta, pandas.DataFrame.query. Exemplo:

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

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.