2012-07-02 25 views
5

Puede alguien explicar a fondo la última línea del código siguiente:Método Python Definición El uso de decoradores

def myMethod(self): 
    # do something 

myMethod = transformMethod(myMethod) 

¿Por qué quieres pasar la definición de un método a través de otro método? ¿Y cómo funcionaría eso? ¡Gracias por adelantado!

Respuesta

2

Este es un ejemplo de ajuste de funciones, que es cuando tiene una función que acepta una función como argumento y devuelve una nueva función que modifica el comportamiento de la función original.

Aquí es un ejemplo de cómo esto podría ser utilizado, se trata de un envoltorio sencillo que sólo imprime 'Enter' y 'Salir' en cada llamada:

def wrapper(func): 
    def wrapped(): 
     print 'Enter' 
     result = func() 
     print 'Exit' 
     return result 
    return wrapped 

Y aquí está un ejemplo de cómo se puede utilizar esto:

>>> def say_hello(): 
...  print 'Hello' 
... 
>>> say_hello() # behavior before wrapping 
Hello 
>>> say_hello = wrapper(say_hello) 
>>> say_hello() # behavior after wrapping 
Enter 
Hello 
Exit 

Para mayor comodidad, Python proporciona la decorator syntax que es sólo una versión abreviada de envoltura función que hace lo mismo en el momento de definición de la función, aquí es cómo esto se puede utilizar:

>>> @wrapper 
... def say_hello(): 
...  print 'Hello' 
... 
>>> say_hello() 
Enter 
Hello 
Exit 
+0

¡Muchas gracias por toda su ayuda! Agradezco mucho lo claro que fue. –

2

¿Por qué le gustaría pasar la definición de un método a través de otro método?

Porque desea modificar su comportamiento.

¿Y cómo funcionaría eso?

Perfectamente, ya que las funciones son de primera clase en Python.

def decorator(f): 
    def wrapper(*args, **kwargs): 
    print 'Before!' 
    res = f(*args, **kwargs) 
    print 'After!' 
    return res 
    return wrapper 

def somemethod(): 
    print 'During...' 

somemethod = decorator(somemethod) 

somemethod() 
+0

¿Por qué no modificas el código fuente actual del método? ¿Puedes dar un ejemplo donde pasar la definición de un método sería muy beneficioso? Por último, ¿cómo se escribiría el método transform para modificar otro método? Perdón por todas las preguntas, nunca había visto este tipo de sintaxis antes, así que estoy muy confundido. –

+2

"¿Por qué no modificas el código fuente actual del método?" Debido a que no puede * tener * el código fuente, o necesita aplicarlo a múltiples funciones. –

+1

@ user1495015 O tal vez la modificación que se hace es siempre la misma, solo la parte de lo que realmente se modifica es diferente. Para esto, puedes felizmente usar decoradores. – glglgl

1

Lo que describes es un decorador, una forma de modificación método/función que se puede lograr mucho más fácil con la sintaxis especial para decorators.

Lo que usted describe es equivalente a

@transformMethod 
def myMethod(self): 
    # do something 

Decoradores se utiliza de forma generalizada, por ejemplo en forma de @staticmethod, @classmethod, @functools.wraps(), @contextlib.contextmanager etc etc etc

Desde un cierto Python versión (creo que era 2.6), las clases también pueden decorarse.

Ambas clases de decoratiors permiten felizmente devolver objetos que ni siquiera son funciones o clases. Por ejemplo, puede decorar una función de generador de una manera que lo convierta en un dict, un conjunto o lo que sea.

apply = lambda tobeapplied: lambda f: tobeapplied(f()) 

@apply(dict) 
def mydict(): 
    yield 'key1', 'value1' 
    yield 'key2', 'value2' 
print mydict 

@apply(set) 
def myset(): 
    yield 1 
    yield 2 
    yield 1 
    yield 4 
    yield 2 
    yield 7 
print myset 

¿Qué hago aquí?

Creo una función que toma una "cosa para aplicar" y a su vez devuelve otra función.

Esta función "interna" toma la función para ser decorada, la llama y pone su resultado en la función externa y devuelve este resultado.

f() devuelve un objeto generador que luego se pone en dict() o set().

+0

No, * es * un decorador. El '@' es solo azúcar sintáctico para la misma operación exacta. –

+0

@ IgnacioVazquez-Abrams Tienes razón. Estaba demasiado cerca de la definición de Python. – glglgl

+0

@ IgnacioVazquez-Abrams sí, pero la mayoría de la gente ve decoradores al revés. No necesariamente como una transformación de método genérico. –

0

Tienes que entender que en Python, todo es un objeto. Una función es un objeto. Puede hacer las mismas cosas con un objeto de función que puede hacer con otros tipos de objetos: almacenar en una lista, almacenar en un diccionario, regresar de una llamada de función, etc.

La razón habitual para el código como usted mostró es "envolver" el otro objeto de función. Por ejemplo, aquí hay un contenedor que imprime el valor devuelto por una función.

def print_wrapper(fn): 
    def new_wrapped_fn(*args): 
     x = fn(*args) 
     print("return value of %s is: %s" % (fn.__name__, repr(x))) 
     return x 
    return new_wrapped_fn 

def test(a, b): 
    return a * b 

test = print_wrapper(test) 

test(2, 3) # prints: return value of test is: 6 

Esta es una tarea tan útil, y tan común, que Python tiene un soporte especial para ella. Búsqueda en Google de "decoradores de Python".

0

En su pregunta original, preguntó "¿Por qué querría pasar la definición de un método a través de otro método?" Luego, en un comentario, preguntaste "¿Por qué no modificas el código fuente real del método?" De hecho, creo que es una muy buena pregunta y difícil de responder sin agitar la mano, porque los decoradores solo se vuelven realmente útiles cuando el código alcanza un cierto nivel de complejidad. Sin embargo, creo que el punto de decoradores será más clara si se tiene en cuenta las siguientes dos funciones:

def add_x_to_sequence(x, seq): 
    result = [] 
    for i in seq: 
     result.append(i + x) 
    return result 

def subtract_x_from_sequence(x, seq): 
    result = [] 
    for i in seq: 
     result.append(i - x) 
    return result 

Ahora, estas dos funciones de ejemplo, tienen algunos defectos - en la vida real, por ejemplo, lo que probablemente acaba de volver a escribir como una lista de comprensiones, pero ignoremos los defectos obvios por el momento, y pretendamos que debe escribirlos de esta manera, como for bucles que iteran sobre las secuencias. Ahora nos enfrentamos al problema de que nuestras dos funciones hacen casi lo mismo, que difieren solo en un momento clave. Eso significa que nos estamos repitiendo aquí! Y eso es un problema Ahora tenemos que mantener más líneas de código, dejando más espacio para que aparezcan los errores, y más espacio para que los errores se oculten una vez que hayan aparecido.

Una aproximación clásica a este problema podría ser la de crear una función que toma una función, y lo aplica a través de una orden, así:

def my_map(func, x, seq): 
    result = [] 
    for i in seq: 
     result.append(func(i, x)) 
    return result 

Ahora todo lo que tenemos que hacer es definir funcs concretos pasar a my_map (que en realidad es solo una versión especializada de la función incorporada map).

def sub(a, b): 
    return a - b 

def add(a, b): 
    return a + b 

Y podemos usarlos como esto:

added = my_map(sub, x, seq) 

Pero este enfoque tiene sus problemas. Por ejemplo, es un poco más difícil de leer que nuestras funciones independientes originales; y cada vez que deseemos sumar o restar x de una lista de elementos, debemos especificar la función y el valor como argumentos. Si estamos haciendo esto mucho, preferimos tener un solo nombre de función que siempre se refiera a la misma acción, que mejoraría la legibilidad y facilitaría la comprensión de lo que está sucediendo en nuestro código. Nos podríamos envolver el anterior en otra función ...

def add_x_to_sequence(x, seq): 
    return my_map(add, x, seq) 

Pero ahora estamos repitiendo pues otra vez!Y también estamos creando una proliferación de funciones, abarrotando nuestro espacio de nombres.

Los decoradores proporcionan una salida a estos problemas. En lugar de pasar una función a otra función cada vez, podemos pasarla una vez. Primero definimos una función de contenedor:

def vectorize(func): 
    def wrapper(x, seq): 
     result = [] 
     for i in seq: 
      result.append(func(i, x)) 
     return result 
    return wrapper 

Ahora todo lo que tenemos que hacer es definir una función y pasarlo a lo anterior, envolviéndolo:

def add_x_to_sequence(a, b): 
    return a + b 
add_x_to_sequence = vectorize(add_x_to_sequence) 

O, usando la sintaxis de decorador:

@vectorize 
def add_x_to_sequence(a, b): 
    return a + b 

Ahora podemos escribir muchas diferentes funciones vectorize d, y nuestra lógica forpara todos ellos que ocurre en un solo pl as. Ahora no tenemos que arreglar u optimizar muchas funciones diferentes por separado; todos nuestros errores relacionados con bucles y optimizaciones relacionadas con bucles ocurren en el mismo lugar; y aún obtenemos todos los beneficios de legibilidad de las funciones especialmente definidas.

Cuestiones relacionadas