¿Cuáles son algunos usos comunes para los decoradores de Python? [cerrado]

337

Si bien me gusta pensar en mí mismo como un codificador de Python razonablemente competente, un aspecto del lenguaje que nunca he podido entender son los decoradores.

Sé cuáles son (superficialmente), he leído tutoriales, ejemplos, preguntas sobre Stack Overflow, y entiendo la sintaxis, puedo escribir la mía, ocasionalmente uso @classmethod y @staticmethod, pero nunca se me ocurre usar un decorador para resolver un problema en mi propio código Python. Nunca encuentro un problema en el que pienso, "Hmm ... ¡esto parece un trabajo para un decorador!"

Entonces, me pregunto si ustedes podrían ofrecer algunos ejemplos de dónde han usado decoradores en sus propios programas, y espero tener un "¡A-ha!" momento y conseguirlos .

Dana
fuente
55
Además, los decoradores son útiles para la memorización, es decir, el almacenamiento en caché de un resultado lento de calcular de una función. El decorador puede devolver una función que verifica las entradas y, si ya se han presentado, devolver un resultado en caché.
Peter
1
Tenga en cuenta que Python tiene un decorador incorporado functools.lru_cache, que hace exactamente lo que Peter dijo, desde Python 3.2, lanzado en febrero de 2011.
Taegyung
El contenido de la biblioteca Python Decorator debería darle una buena idea de otros usos para ellos.
Martineau

Respuestas:

126

Yo uso decoradores principalmente para propósitos de tiempo

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
fuente
13
Bajo Unix, time.clock()mide el tiempo de CPU. Es posible que desee utilizar time.time()en su lugar si desea medir el tiempo del reloj de pared.
Jabba
20
Gran ejemplo! Sin embargo, no tengo idea de lo que hace. Una explicación de lo que está haciendo allí y cómo el decorador resuelve el problema sería muy agradable.
MeLight
77
Bueno, mide el tiempo que lleva myFunctioncorrer ...
RSabet
98

Los he usado para la sincronización.

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

Como se señaló en los comentarios, desde Python 2.5 puede usar una withdeclaración junto con un objeto threading.Lock(o multiprocessing.Lockdesde la versión 2.6) para simplificar la implementación del decorador para simplemente:

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

De todos modos, entonces lo usas así:

import threading
lock = threading.Lock()

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

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

Básicamente solo pone lock.acquire()/ lock.release()a cada lado de la llamada a la función.

John Fouhy
fuente
18
Posiblemente justificado, pero los decoradores son intrínsecamente confusos, especialmente. a los novatos de primer año que vienen detrás de ti y tratan de modificar tu código. Evite esto con simplicidad: solo haga que do_something () encierre su código en un bloque debajo de 'con bloqueo:' y todos pueden ver claramente su propósito. Los decoradores son utilizados en exceso por personas que quieren parecer inteligentes (y muchos en realidad lo son), pero luego el código llega a los simples mortales y se ensucia.
Kevin J. Rice
18
@ KevinJ.Rice Restringir su código para que los 'novatos de primer año' puedan entenderlo mejor es una práctica terrible. La sintaxis del decorador es mucho más fácil de leer y desacopla mucho el código.
TaylerJones
18
@TaylerJones, la legibilidad del código es mi prioridad más alta cuando escribo. El código se lee más de 7 veces por cada vez que se modifica. El código difícil de entender (para novatos o para expertos que trabajan bajo la presión del tiempo) es una deuda técnica que debe pagarse cada vez que alguien visita el árbol de origen.
Kevin J. Rice
@TaylerJones Una de las tareas más importantes para un programador es brindar claridad.
JDOaktown
71

Utilizo decoradores para los parámetros de verificación de tipo que se pasan a mis métodos de Python a través de alguna RMI. Entonces, en lugar de repetir el mismo recuento de parámetros, mumbo-jumbo que genera excepciones una y otra vez.

Por ejemplo, en lugar de:

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

Solo declaro:

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

y accepts()hace todo el trabajo por mí.

Simón
fuente
15
Para cualquier persona interesada, hay una implementación @acceptsen PEP 318.
Martineau
2
Creo que hay un error tipográfico ... el primer método debe ser aceptado ... declaró ambos como "myMethod"
DevC
1
@DevC No, no parece un error tipográfico. Dado que claramente no es una implementación de "acepta (..)", y aquí "acepta (..)" realiza el trabajo que de lo contrario se haría por las dos líneas al comienzo de "myMethod (..)" - esa es la única interpretación que encaja.
Evgeni Sergeev
1
Perdón por el golpe, solo quería señalar que verificar el tipo de argumentos pasados ​​y plantear un TypeError de lo contrario se considera una mala práctica porque no va a aceptar, por ejemplo, un int si verifica solo flotantes, y porque normalmente el el código en sí mismo debe adaptarse a diferentes tipos de valores pasados ​​para una máxima flexibilidad.
Gustavo6046
2
La forma recomendada de verificar el tipo en Python es a través de la isinstance()función incorporada, como se hace en la implementación PEP 318 del decorador. Dado que su classinfoargumento puede ser de uno o más tipos, su uso también mitigaría las objeciones (válidas) de @ Gustavo6046. Python también tiene una Numberclase base abstracta, por lo que isinstance(42, numbers.Number)es posible realizar pruebas muy genéricas .
Martineau
48

Los decoradores se utilizan para cualquier cosa que desee "envolver" de forma transparente con funcionalidad adicional.

Django los usa para envolver la funcionalidad de "inicio de sesión requerido" en las funciones de visualización , así como para registrar funciones de filtro .

Puede usar decoradores de clases para agregar registros con nombre a las clases .

Cualquier funcionalidad suficientemente genérica que pueda "agregar" al comportamiento de una clase o función existente es un juego justo para la decoración.

También hay una discusión de casos de uso en el grupo de noticias Python-Dev señalado por PEP 318 - Decoradores para funciones y métodos .

cdleary
fuente
Cherrypy utiliza @ cherrypy.expose para mantener en claro qué funciones son públicas y cuáles son ocultas. Esa fue mi primera presentación y me acostumbré desde allí.
Marc Maxmeister el
26

Para las pruebas nasales, puede escribir un decorador que proporcione una función o método de prueba unitaria con varios conjuntos de parámetros:

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

La biblioteca Twisted usa decoradores combinados con generadores para dar la ilusión de que una función asincrónica es sincrónica. Por ejemplo:

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

Con esto, el código que se habría dividido en un montón de pequeñas funciones de devolución de llamada se puede escribir de forma natural como un solo bloque, lo que hace que sea mucho más fácil de entender y mantener.

DNS
fuente
14

Un uso obvio es para iniciar sesión, por supuesto:

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()
SeñorMetaphor
fuente
10

Los uso principalmente para la depuración (envoltura alrededor de una función que imprime sus argumentos y resultados) y verificación (por ejemplo, para verificar si un argumento es del tipo correcto o, en el caso de una aplicación web, si el usuario tiene suficientes privilegios para llamar a un particular método).

DzinX
fuente
6

Estoy usando el siguiente decorador para hacer que una función sea segura. Hace que el código sea más legible. Es casi similar al propuesto por John Fouhy, pero la diferencia es que uno trabaja en una sola función y que no hay necesidad de crear un objeto de bloqueo explícitamente.

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
fuente
1
¿Significa esto que cada función, tan decorada, tiene su propia cerradura?
duelo
1
@grieve sí, cada vez que se usa el decorador (llamado) crea un nuevo objeto de bloqueo para la función / método que se está decorando.
Martineau
55
Eso es realmente peligroso. El método inc_var () es "threadsafe" porque solo una persona puede llamarlo a la vez. Dicho esto, dado que el método opera en la variable miembro "var" y presumiblemente otros métodos también pueden operar en la variable miembro "var" y esos accesos no son seguros ya que el bloqueo no se comparte. Hacer las cosas de esta manera le da al usuario de clase X una falsa sensación de seguridad.
Bob Van Zant
Eso no es seguro para roscas hasta que se use un solo bloqueo.
Chandu
5

Los decoradores se usan para definir las propiedades de una función o como repetitivo que la altera; Es posible, pero contra-intuitivo, que devuelvan funciones completamente diferentes. Mirando las otras respuestas aquí, parece que uno de los usos más comunes es limitar el alcance de algún otro proceso, ya sea registro, creación de perfiles, controles de seguridad, etc.

CherryPy utiliza el envío de objetos para hacer coincidir las URL con los objetos y, finalmente, los métodos. Los decoradores en esos métodos indican si CherryPy incluso tiene permitido usar esos métodos. Por ejemplo, adaptado del 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
fuente
Esto no es verdad. Un decorador puede cambiar completamente el comportamiento de una función.
recursivo el
Bueno. Pero, ¿con qué frecuencia un decorador "cambia por completo el comportamiento de una función?" Por lo que he visto, cuando no se usan para especificar propiedades, solo se usan para código repetitivo. He editado mi respuesta.
Nikhil Chelliah el
5

Los usé recientemente, mientras trabajaba en la aplicación web de redes sociales. Para la comunidad / grupos, se suponía que debía autorizar la membresía para crear una nueva discusión y responder a un mensaje de que debe ser miembro de ese grupo en particular. Entonces, escribí un decorador @membership_requiredy lo puse donde lo requería en mi opinión.

aatifh
fuente
1

Yo uso este decorador para arreglar el 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']

esto está escrito cuando refactorizo ​​algunas funciones necesitan pasar el argumento "wanN" pero en mis viejos códigos, pasé N o 'N' solamente

HVNSweeting
fuente
1

Decorator se puede utilizar para crear fácilmente variables de método de función.

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
usuario1476056
fuente
66
Gracias por su ejemplo, pero (disculpas) tengo que decir WTF - ¿Por qué usarías esto? Tiene un enorme potencial para confundir a las personas. Por supuesto, respeto las necesidades de los usos de los casos límite, pero te encuentras con un problema común que tienen muchos desarrolladores de Python inexpertos: no usar las clases lo suficiente. Es decir, solo tiene una clase simple de recuento de clases, inicialícela y úsela. Los novatos tienden a escribir drop-thru (código no basado en clases) e intentan lidiar con la falta de funcionalidad de clase con soluciones elaboradas. Por favor no ¿Por favor? Lamento tocar el arpa, gracias por tu respuesta, pero me has presionado.
Kevin J. Rice
Estaría -1 en esto si apareciera como una solicitud de extracción para que revise el código, y también estoy -1 en esto como un buen python.
Techdragon
Lindo. Tonto, pero lindo. :) No me importa el atributo de función ocasional, pero son algo tan raro en el código típico de Python que si voy a usar uno, prefiero hacerlo explícitamente, en lugar de ocultarlo debajo de un decorador.
PM 2Ring