2012-09-15 11 views
6

Estoy tratando de entender cómo funcionan los decoradores, y me preguntaba si una función decorada puede acceder a las variables del decorador. Por ejemplo, en el siguiente código, ¿cómo hago para que f1 tenga acceso a localVariable? ¿Es eso posible, y es incluso una buena forma de hacer las cosas?Puede una función decorada acceder a las variables del decorador

def funcDec(func): 
    localVariable = "I'm a local string" 
    def func2Return(*args):                    
     print "Calling localVariable from decorator " + localVariable 
     func(*args)          
     print "done with calling f1" 
    return func2Return 

@funcDec 
def f1(x, y): 
    print x + y 
    print localVariable 

f1(2, 3) 

Respuesta

4

No, no puede. Ver this previous question. El hecho de que la función sea decoradora no significa que las funciones a las que llama tengan acceso especial a sus variables. Si hace esto:

def func(): 
    a = 2 
    otherFunc() 

Entonces otherFunc no tiene acceso a la variable a. Así es como funciona para todas las llamadas a funciones, y también funciona para los decoradores.

Ahora, la función de contenedor que define el interior del decorador (func2Return en su ejemplo) hace tienen acceso a las variables, debido a que la función es léxicamente en el mismo alcance que esas variables. Entonces su línea print "Calling localVariable from decorator " + localVariable funcionará. Puede usar esto hasta cierto punto para envolver la función decorada con un comportamiento que depende de las variables en el decorador. Pero la función que en realidad está decorada (f1 en su ejemplo) no tiene acceso a esas variables.

Una función solo tiene acceso a las variables locales desde el ámbito en el que realmente se encuentra la definición de la función. Las funciones no obtienen variables de los ámbitos de llamada. (Esta es una buena cosa. Si lo hicieran, sería un gran lío.)

+0

Ver también: http://en.wikipedia.org/wiki/Scope_(computer_science)#Lexical_scoping_and_dynamic_scoping – georg

5

Creo que ayuda si se tiene en cuenta que un decorador

@deco 
def f(...): ... 

es el azúcar solo sintáctica para

def f(...): ... 
f = deco(f) 

en lugar de algún tipo de expansión de macro. En Python, el alcance de una variable se determina estáticamente, por lo que para una función global (nivel de módulo) una variable que no se pasa como un argumento ni se asigna se buscará en el espacio de nombre global.

Por lo tanto, debe transmitir explícitamente una variable local de func2Return(). Cambiar la firma de f1 a f1(x, y, localvariable=None) y tienen la función de contenedor fun2Return llaman con

f1(*args, localvariable=localvariable) 
7

Debido a reglas de alcance de Python, una función decorada en general no puede acceder a cualquier variable en el decorador. Sin embargo, puesto que las funciones pueden tener atributos arbitrarios asignados a ellos, podría hacer algo como lo siguiente en el decorador para conseguir un efecto similar:

def funcDec(func): 
    localVariable = "I'm a local string" 

    def wrapped(*args): 
     print("Calling localVariable from funcDec " + localVariable) 
     func(*args) 
     print("done with calling f1") 

    wrapped.attrib = localVariable 
    return wrapped 

@funcDec 
def f1(x, y): 
    print(x + y) 
    print('f1.attrib: {!r}'.format(f1.attrib)) 

f1(2, 3) 

que produciría el siguiente resultado:

Calling localVariable from decorator I'm a local string 
5 
f1.attrib: "I'm a local string" 
done with calling f1 

Alguien preguntó si esto podría aplicarse a los métodos de una clase: La respuesta es "sí", pero debe hacer referencia al método ya sea a través de la clase en sí o la instancia de que pasó como self argumento. Ambas técnicas se muestran a continuación. Es preferible usar self ya que hace que el código sea independiente del nombre de la clase en la que se encuentra.

class Test(object): 
    @funcDec 
    def f1(self): 
     print('{}.f1() called'.format(self.__class__.__name__)) 
     print('self.f1.attrib: {!r}'.format(self.f1.attrib)) # Preferred. 
     print('Test.f1.attrib: {!r}'.format(Test.f1.attrib)) # Also works. 

print() 
test = Test() 
test.f1() 

Salida:

Calling localVariable from funcDec I'm a local string 
Test.f1() called 
self.f1.attrib: "I'm a local string" 
Test.f1.attrib: "I'm a local string" 
done with calling f1 
+0

gran ejemplo! ¿Se puede hacer lo mismo con un método en una clase? –

+0

@KeerthanaPrabhakaran: Sí, el decorador trabajaría en métodos de clase, pero necesitarían hacer referencia al atributo agregado de manera diferente: es decir, algo así como 'self.method_name.attrib'. – martineau

+0

¡Sí, lo intenté! Pero recibí una excepción que decía que el objeto 'instancemethod' no tiene el atributo 'attrib'' –

Cuestiones relacionadas