Quais são alguns usos comuns para decoradores de Python? [fechadas]

336

Embora eu goste de me considerar um codificador Python razoavelmente competente, um aspecto da linguagem que nunca consegui entender são os decoradores.

Sei o que são (superficialmente), li tutoriais, exemplos, perguntas sobre o Stack Overflow e entendo a sintaxe, posso escrever minha própria, ocasionalmente use @classmethod e @staticmethod, mas nunca me ocorre usar um decorador para resolver um problema no meu próprio código Python. Nunca encontro um problema em que penso: "Hmm ... isso parece um trabalho para um decorador!"

Então, eu estou querendo saber se vocês podem oferecer alguns exemplos de onde você usou decoradores em seus próprios programas, e espero ter um "A-ha!" momento e obtê- los.

Dana
fonte
5
Além disso, os decoradores são úteis para memorizar - que está armazenando em cache o resultado lento de uma função de computação. O decorador pode retornar uma função que verifica as entradas e, se já tiverem sido apresentadas, retornar um resultado em cache.
Peter
11
Note que Python tem um built-in decorador, functools.lru_cacheque faz exatamente o que Pedro disse, desde Python 3.2, lançado em fevereiro de 2011.
Taegyung
O conteúdo da biblioteca do decorador Python deve fornecer uma boa idéia de outros usos para eles.
martineau

Respostas:

126

Eu uso decoradores principalmente para fins de tempo

def time_dec(func):

  def wrapper(*arg):
      t = time.clock()
      res = func(*arg)
      print func.func_name, time.clock()-t
      return res

  return wrapper


@time_dec
def myFunction(n):
    ...
RSabet
fonte
13
No Unix, time.clock()mede o tempo da CPU. Você pode usar time.time()se preferir medir o tempo do relógio de parede.
Jabba
20
Ótimo exemplo! Não faço ideia do que faz. Uma explicação do que você está fazendo lá e como o decorador resolve o problema seria muito bom.
MeLight
7
Bem, ele mede o tempo que leva para myFunctionexecutar ...
RSabet
98

Eu os usei para sincronização.

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            lock.acquire()
            try:
                return f(*args, **kw)
            finally:
                lock.release()
        return newFunction
    return wrap

Conforme apontado nos comentários, desde o Python 2.5 você pode usar uma withdeclaração em conjunto com um objeto threading.Lock(ou multiprocessing.Lockdesde a versão 2.6) para simplificar a implementação do decorador para apenas:

import functools

def synchronized(lock):
    """ Synchronization decorator """
    def wrap(f):
        @functools.wraps(f)
        def newFunction(*args, **kw):
            with lock:
                return f(*args, **kw)
        return newFunction
    return wrap

Independentemente disso, você o usa assim:

import threading
lock = threading.Lock()

@synchronized(lock)
def do_something():
  # etc

@synchronzied(lock)
def do_something_else():
  # etc

Basicamente, apenas coloca lock.acquire()/ lock.release()em ambos os lados da chamada de função.

John Fouhy
fonte
17
Possivelmente justificado, mas os decoradores são inerentemente confusos, esp. aos noobs do primeiro ano que estão atrás de você e tentam modificar seu código. Evite isso com simplicidade: basta fazer com que do_something () coloque seu código em um bloco em 'with lock:' e todos possam ver claramente seu objetivo. Decoradores são amplamente usados ​​por pessoas que querem parecer inteligentes (e muitos são de fato), mas então o código chega a meros mortais e é afetado.
Kevin J. Rice,
18
@ KevinJ.Rice Restringir seu código para que os 'novatos do primeiro ano' possam entender melhor que é uma prática terrível. A sintaxe do decorador é muito mais fácil de ler e desacopla bastante o código.
TaylerJones
18
@ TaylerJones, a legibilidade do código é quase a minha maior prioridade ao escrever. O código é lido mais de 7 vezes para cada vez que é modificado. Código difícil de entender (para noobs ou especialistas que trabalham com pressão de tempo) é uma dívida técnica que deve ser paga toda vez que alguém visita a árvore de origem.
26515 Kevin Kevin Rice
@TaylerJones Uma das tarefas mais importantes para um programador é proporcionar clareza.
JDOaktown
71

Uso decoradores para parâmetros de verificação de tipo que são passados ​​para meus métodos Python por meio de alguma RMI. Então, em vez de repetir o mesmo parâmetro de contagem, crie mumbo-jumbo de exceção várias vezes.

Por exemplo, em vez de:

def myMethod(ID, name):
    if not (myIsType(ID, 'uint') and myIsType(name, 'utf8string')):
        raise BlaBlaException() ...

Eu apenas declaro:

@accepts(uint, utf8string)
def myMethod(ID, name):
    ...

e accepts()faz todo o trabalho para mim.

Simon
fonte
15
Para quem estiver interessado, há uma implementação @acceptsem PEP 318.
martineau
2
Acho que há erro de digitação .. O primeiro método deve ser aceita .. você declarou tanto como "myMethod"
DevC
11
@ DevC Não, não parece um erro de digitação. Como isso claramente não é uma implementação de "accept (..)", e aqui "accept (..)" faz o trabalho que seria feito pelas duas linhas no início de "myMethod (..)" - esse é o única interpretação que se encaixa.
precisa
11
Desculpe pelo problema, eu só queria salientar que verificar o tipo de argumentos passados ​​e gerar um TypeError, caso contrário, é considerado uma prática ruim porque não aceitará, por exemplo, um int se verificar apenas flutuações e porque normalmente o o próprio código deve se adaptar a diferentes tipos de valores passados ​​para obter o máximo de flexibilidade.
precisa saber é o seguinte
2
A maneira recomendada de verificar o tipo no Python é através da isinstance()função interna, como é feito na implementação do decorador do PEP 318 . Como seu classinfoargumento pode ser de um ou mais tipos, usá-lo também mitigaria as objeções (válidas) de @ Gustavo6046. O Python também possui uma Numberclasse base abstrata, portanto testes muito genéricos isinstance(42, numbers.Number)são possíveis.
martineau
48

Os decoradores são usados ​​para qualquer coisa que você queira "envolver" de forma transparente com funcionalidade adicional.

O Django os usa para agrupar a funcionalidade "login required" nas funções de visualização , bem como para registrar funções de filtro .

Você pode usar decoradores de classe para adicionar logs nomeados às classes .

Qualquer funcionalidade suficientemente genérica que você possa "aderir" a uma classe ou comportamento de função existente é um jogo justo para decoração.

Há também uma discussão de casos de uso no grupo de notícias Python-Dev apontado pelo PEP 318 - Decoradores de funções e métodos .

cdleary
fonte
Cherrypy usa @ cherrypy.expose para manter diretas quais funções são públicas e quais são ocultas. Essa foi a minha primeira introdução e me acostumei a isso lá.
Marc Maxmeister
26

Para testes nos, você pode escrever um decorador que forneça uma função ou método de teste de unidade com vários conjuntos de parâmetros:

@parameters(
   (2, 4, 6),
   (5, 6, 11),
)
def test_add(a, b, expected):
    assert a + b == expected
Torsten Marek
fonte
23

A biblioteca Twisted usa decoradores combinados com geradores para dar a ilusão de que uma função assíncrona é síncrona. Por exemplo:

@inlineCallbacks
def asyncf():
    doStuff()
    yield someAsynchronousCall()
    doStuff()
    yield someAsynchronousCall()
    doStuff()

Usando isso, o código que teria sido dividido em várias funções de retorno de chamada pode ser escrito naturalmente como um único bloco, facilitando muito o entendimento e a manutenção.

DNS
fonte
14

Um uso óbvio é para o log, é claro:

import functools

def log(logger, level='info'):
    def log_decorator(fn):
        @functools.wraps(fn)
        def wrapper(*a, **kwa):
            getattr(logger, level)(fn.__name__)
            return fn(*a, **kwa)
        return wrapper
    return log_decorator

# later that day ...
@log(logging.getLogger('main'), level='warning')
def potentially_dangerous_function(times):
    for _ in xrange(times): rockets.get_rocket(NUCLEAR=True).fire()
MisterMetaphor
fonte
10

Eu os uso principalmente para depuração (wrapper em torno de uma função que imprime seus argumentos e resultado) e verificação (por exemplo, para verificar se um argumento é do tipo correto ou, no caso de aplicativo da Web, se o usuário tem privilégios suficientes para chamar um determinado método).

DzinX
fonte
6

Estou usando o decorador a seguir para tornar uma função segura para threads. Isso torna o código mais legível. É quase semelhante ao proposto por John Fouhy, mas a diferença é que se trabalha em uma única função e que não há necessidade de criar explicitamente um objeto de bloqueio.

def threadsafe_function(fn):
    """decorator making sure that the decorated function is thread safe"""
    lock = threading.Lock()
    def new(*args, **kwargs):
        lock.acquire()
        try:
            r = fn(*args, **kwargs)
        except Exception as e:
            raise e
        finally:
            lock.release()
        return r
    return new

class X:
    var = 0

    @threadsafe_function     
    def inc_var(self):
        X.var += 1    
        return X.var
luc
fonte
11
Isso significa que cada função, tão decorada, tem seu próprio bloqueio?
usar o seguinte
11
@grieve yes, toda vez que o decorador é usado (chamado), ele cria um novo objeto de bloqueio para a função / método que está sendo decorado.
27510 martineau
5
Isso é realmente perigoso. O método inc_var () é "thread-safe", pois somente uma pessoa pode chamá-lo por vez. Dito isto, como o método opera na variável membro "var" e, presumivelmente, outros métodos também podem operar na variável membro "var" e esses acessos não são seguros para threads, pois o bloqueio não é compartilhado. Fazer as coisas dessa maneira fornece ao usuário da classe X uma falsa sensação de segurança.
Bob Van Zant
Isso não é seguro para threads até que um único bloqueio seja usado.
Chandu
5

Decoradores são usados ​​para definir as propriedades de uma função ou como um padrão que a altera; é possível, mas é contra-intuitivo retornar funções completamente diferentes. Observando as outras respostas aqui, parece que um dos usos mais comuns é limitar o escopo de outro processo - seja log, criação de perfil, verificações de segurança, etc.

CherryPy usa o envio de objetos para corresponder URLs a objetos e, eventualmente, métodos. Decoradores sobre esses métodos sinalizar ou não CherryPy é ainda permitido usar esses métodos. Por exemplo, adaptado do tutorial :

class HelloWorld:

    ...

    def secret(self):
        return "You shouldn't be here."

    @cherrypy.expose
    def index(self):
        return "Hello world!"

cherrypy.quickstart(HelloWorld())
Nikhil Chelliah
fonte
Isso não é verdade. Um decorador pode mudar completamente o comportamento de uma função.
recursiva
OK. Mas com que frequência um decorador "muda completamente o comportamento de uma função?" Pelo que vi, quando não são usadas para especificar propriedades, são usadas apenas para o código padrão. Eu editei minha resposta.
Nikhil Chelliah
5

Eu os usei recentemente, enquanto trabalhava em aplicativos da Web para redes sociais. Para Comunidade / Grupos, eu deveria dar autorização de associação para criar uma nova discussão e responder a uma mensagem de que você precisa ser membro desse grupo específico. Então, eu escrevi um decorador @membership_requirede coloquei o que eu precisava na minha opinião.

aatifh
fonte
1

Eu uso esse decorador para corrigir parâmetro

def fill_it(arg):
    if isinstance(arg, int):
        return "wan" + str(arg)
    else:
        try:
            # number present as string
            if str(int(arg)) == arg:
                return "wan" + arg
            else:
                # This should never happened
                raise Exception("I dont know this " + arg)
                print "What arg?"
        except ValueError, e:
            return arg

def fill_wanname(func):
    def wrapper(arg):
        filled = fill_it(arg)
        return func(filled)
    return wrapper

@fill_wanname
def get_iface_of(wanname):
    global __iface_config__
    return __iface_config__[wanname]['iface']

isto escrito quando refatorar algumas funções precisa passar o argumento "wanN", mas nos meus códigos antigos eu passei somente N ou 'N'

HVNSweeting
fonte
1

O Decorator pode ser usado para criar facilmente variáveis ​​de método de função.

def static_var(varname, value):
    '''
    Decorator to create a static variable for the specified function
    @param varname: static variable name
    @param value: initial value for the variable
    '''
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("count", 0)
def mainCallCount():
    mainCallCount.count += 1
user1476056
fonte
6
Obrigado pelo seu exemplo, mas (desculpas) eu tenho que dizer WTF - Por que você usaria isso? Tem um enorme potencial para confundir pessoas. Obviamente, eu respeito as necessidades de uso de casos extremos, mas você está enfrentando um problema comum que muitos desenvolvedores Python inexperientes têm - não usando classes o suficiente. Ou seja, basta ter uma classe simples var de count, inicializá-lo e usá-lo. Noobs tendem a escrever drop-thru (código não baseado em classe) e tentam lidar com a falta de funcionalidade de classe com soluções elaboradas. Por favor não? Por favor? desculpe harpa, obrigado pela resposta, mas você apertou um botão de atalho para mim.
Kevin J. Rice,
Eu ficaria -1 nisso se aparecesse como uma solicitação de pull para que eu revisasse o código, e por isso também estou -1 nisso como um bom python.
Techdragon
Fofa. Parvo, mas fofo. :) Eu não me importo com o atributo de função ocasional, mas eles são uma coisa tão rara no código Python típico que, se eu for usar um, prefiro fazê-lo explicitamente, em vez de ocultá-lo em um decorador.
PM 2Ring