2012-01-09 14 views
12

Escribo un decorador, y por varias razones molestas [0] sería conveniente comprobar si la función que está envolviendo se está definiendo por separado o como parte de una clase (y además qué clases es esa nueva clase subclasificación).Python: ¿puede un decorador determinar si una función se está definiendo dentro de una clase?

Por ejemplo:

def my_decorator(f): 
    defined_in_class = ?? 
    print "%r: %s" %(f, defined_in_class) 

@my_decorator 
def foo(): pass 

class Bar(object): 
    @my_decorator 
    def bar(self): pass 

debe imprimir:

<function foo …>: False 
<function bar …>: True 

Además, tenga en cuenta lo siguiente:

  • En los decoradores de puntos se aplicarán a la función seguirá siendo una función, no un método independiente, por lo que las pruebas para el método de instancia/sin consolidar (usando typeof o inspect) no t trabajo.
  • Por favor, sólo ofrecen sugerencias que resuelven este problema - Soy consciente de que hay muchas maneras similares para llevar a cabo este fin (ex, utilizando un decorador de clase), pero me gustaría que sucedan en la decoración tiempo, después no.

[0]: específicamente, estoy escribiendo un decorador que hará que sea fácil hacer pruebas parametrizadas con nose. Sin embargo, noseno ejecuta generadores de prueba en las subclases de unittest.TestCase, por lo que me gustaría que mi decorador pueda determinar si se está utilizando dentro de una subclase de TestCase y falla con un error apropiado. La solución obvia: utilizar isinstance(self, TestCase) antes de llamar a la función envuelta no funciona, porque la función envuelta necesita para ser un generador, que no se ejecuta en total.

+0

Para los curiosos, este es el resultado: http://paste.pocoo.org/show/532430/ –

Respuesta

11

Eche un vistazo a la salida de inspect.stack() cuando ajusta un método. Cuando la ejecución de su decorador está en marcha, el marco de pila actual es la llamada de función a su decorador; el siguiente fotograma de acumulación es la acción de ajuste @ que se aplica al nuevo método; y el tercer marco será la definición de la clase en sí misma, que merece un marco de pila separado porque la definición de clase es su propio espacio de nombres (que se completa para crear una clase cuando se termina de ejecutar).

Sugiero, por lo tanto:

defined_in_class = (len(frames) > 2 and 
        frames[2][4][0].strip().startswith('class ')) 

Si todos esos índices mirada loca imposible de mantener, a continuación, puede ser más explícita mediante la adopción de la estructura aparte pieza por pieza, de esta manera:

import inspect 
frames = inspect.stack() 
defined_in_class = False 
if len(frames) > 2: 
    maybe_class_frame = frames[2] 
    statement_list = maybe_class_frame[4] 
    first_statment = statement_list[0] 
    if first_statment.strip().startswith('class '): 
     defined_in_class = True 

Tenga en cuenta que hago no vea alguna forma de preguntarle a Python sobre el nombre de clase o la jerarquía de herencia en el momento en que se ejecuta su envoltorio; ese punto es "demasiado pronto" en los pasos de procesamiento, ya que la creación de la clase aún no ha terminado. Analice la línea que comienza con class usted mismo y luego mire en los globales para encontrar la superclase, o toque el objeto de código frames[1] para ver lo que puede aprender: parece que el nombre de clase termina siendo frames[1][0].f_code.co_name en el código anterior , pero no encuentro ninguna forma de saber qué superclases se adjuntarán cuando finalice la creación de la clase.

-2

creo que las funciones del módulo inspect van a hacer lo que quiere, sobre todo isfunction y ismethod:

>>> import inspect 
>>> def foo(): pass 
... 
>>> inspect.isfunction(foo) 
True 
>>> inspect.ismethod(foo) 
False 
>>> class C(object): 
...  def foo(self): 
...    pass 
... 
>>> inspect.isfunction(C.foo) 
False 
>>> inspect.ismethod(C.foo) 
True 
>>> inspect.isfunction(C().foo) 
False 
>>> inspect.ismethod(C().foo) 
True 

A continuación, puede seguir el Types and Members table acceder a la función dentro del método dependiente o independiente:

>>> C.foo.im_func 
<function foo at 0x1062dfaa0> 
>>> inspect.isfunction(C.foo.im_func) 
True 
>>> inspect.ismethod(C.foo.im_func) 
False 
+0

Espero que 'inspect' esté involucrado, pero no de la manera que usted ha descrito. Vea mi primera nota (que en el momento en que se aplican los decoradores, la función es solo una instancia de 'función', no' método unbound' o 'método enlazado'). –

+0

No lo dijo explícitamente, pero su punto era que esto no funciona en el tiempo de definición de clase. –

+0

Sí, tiene razón: he actualizado mi pregunta para que sea más explícita. Agradezco la respuesta completa, simplemente no aborda mi pregunta. –

2

alguna solución hacky que tengo:

import inspect 

def my_decorator(f): 
    args = inspect.getargspec(f).args 
    defined_in_class = bool(args and args[0] == 'self') 
    print "%r: %s" %(f, defined_in_class) 

Pero se retransmite en la presencia del argumento self en la función.

1

Puede comprobar si el propio decorador se está llamando a nivel de módulo o anidado dentro de otra cosa.

defined_in_class = inspect.currentframe().f_back.f_code.co_name != "<module>" 
+0

Hrm ... Pero esto no funcionaría para las clases anidadas en algo, ¿o sí? (por ejemplo, 'def mk_class(): class MyClass: ...; return MyClass') –

+0

Lo que hace o devuelve la función no afecta el valor de' inspect.currentframe(). f_back'; lo único que importa es dónde se llama a 'my_decorator', y ese siempre será el mismo nivel en el que se define lo que rodea. – chepner

+0

Pude haber malinterpretado tu comentario; cualquier función decorada dentro de otras funciones no ejecutará el decorador hasta que se llame a la función envolvente, pero el alcance de la función envolvente se calculará correctamente. Puede usar esto dentro del decorador para ver una lista de los ámbitos adjuntos en el punto donde se ejecuta el decorador: '[x [0] .f_code.co_name para x en inspect.stack() [1:]]'. – chepner

2

Un poco tarde a la fiesta aquí, pero esto ha demostrado ser un medio fiable de determinar si un decorador está siendo utilizado en una función definida en una clase:

frames = inspect.stack() 

className = None 
for frame in frames[1:]: 
    if frame[3] == "<module>": 
     # At module level, go no further 
     break 
    elif '__module__' in frame[0].f_code.co_names: 
     className = frame[0].f_code.co_name 
     break 

La ventaja de este método sobre la respuesta aceptada es que funciona con, por ejemplo, py2exe.

Cuestiones relacionadas