programacion poo otra orientada objetos metodos metodo llamar instancia importar herencia clases clase python class methods decorator inspect

poo - Cómo obtener todos los métodos de una clase de python con decorador determinado



poo python 3 (3)

¿Cómo obtener todos los métodos de una determinada clase A que están decorados con @ decorator2?

class A(): def method_a(self): pass @decorator1 def method_b(self, b): pass @decorator2 def method_c(self, t=5): pass


Método 1: Decorador de registro básico

Ya respondí esta pregunta aquí: Llamar funciones por índice de matriz en Python =)

Método 2: análisis de código fuente

Si no tienes control sobre la definición de clase , que es una interpretación de lo que te gustaría suponer, esto es imposible (sin código-lectura-reflexión), ya que por ejemplo el decorador podría ser un decorador no operativo (como en mi ejemplo vinculado) que simplemente devuelve la función sin modificaciones. (Sin embargo, si se permite envolver / redefinir los decoradores, consulte el Método 3: Convertir los decoradores en "autoconscientes" , luego encontrará una solución elegante)

Es un hack terriblemente terrible, pero podrías usar el módulo de inspect para leer el código fuente y analizarlo. Esto no funcionará en un intérprete interactivo, porque el módulo de inspección se negará a dar el código fuente en modo interactivo. Sin embargo, a continuación hay una prueba de concepto.

#!/usr/bin/python3 import inspect def deco(func): return func def deco2(): def wrapper(func): pass return wrapper class Test(object): @deco def method(self): pass @deco2() def method2(self): pass def methodsWithDecorator(cls, decoratorName): sourcelines = inspect.getsourcelines(cls)[0] for i,line in enumerate(sourcelines): line = line.strip() if line.split(''('')[0].strip() == ''@''+decoratorName: # leaving a bit out nextLine = sourcelines[i+1] name = nextLine.split(''def'')[1].split(''('')[0].strip() yield(name)

¡Funciona!:

>>> print(list( methodsWithDecorator(Test, ''deco'') )) [''method'']

Tenga en cuenta que hay que prestar atención al análisis sintáctico y la sintaxis de Python, por ejemplo, @deco y @deco(... son resultados válidos, pero @deco2 no debería devolverse si simplemente solicitamos ''deco'' . Notamos que de acuerdo con el la sintaxis oficial de python en http://docs.python.org/reference/compound_stmts.html decoradores es la siguiente:

decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE

Respiramos con alivio por no tener que lidiar con casos como @(deco) . Pero tenga en cuenta que esto aún no lo ayuda si realmente tiene decoradores realmente complicados, como @getDecorator(...) , por ejemplo

def getDecorator(): return deco

Por lo tanto, esta estrategia de código de análisis que mejor se puede hacer no puede detectar casos como este. Aunque si está utilizando este método, lo que realmente getDecorator es lo que está escrito sobre el método en la definición, que en este caso es getDecorator .

De acuerdo con la especificación, también es válido tener @foo1.bar2.baz3(...) como decorador. Puede extender este método para trabajar con eso. También podría ampliar este método para devolver un <function object ...> lugar del nombre de la función, con mucho esfuerzo. Este método sin embargo es hackish y terrible.

Método 3: Convertir los decoradores en "autoconscientes"

Si no tiene control sobre la definición del decorador (que es otra interpretación de lo que le gustaría), todos estos problemas desaparecerán porque tiene control sobre cómo se aplica el decorador. Por lo tanto, puede modificar el decorador envolviéndolo , crear su propio decorador y usarlo para decorar sus funciones. Permítanme decirlo una vez más: puede hacer que un decorador decore el decorador sobre el que no tiene control, "iluminándolo", lo que en nuestro caso lo hace hacer lo que estaba haciendo antes, pero también .decorator una propiedad de metadato .decorator al que se puede .decorator regresa, lo que le permite realizar un seguimiento de "¿Estaba decorada o no esta función? ¡Comprobemos function.decorator!". Y luego puede iterar sobre los métodos de la clase, y simplemente verifique si el decorador tiene la propiedad .decorator apropiada. =) Como se demuestra aquí:

def makeRegisteringDecorator(foreignDecorator): """ Returns a copy of foreignDecorator, which is identical in every way(*), except also appends a .decorator property to the callable it spits out. """ def newDecorator(func): # Call to newDecorator(method) # Exactly like old decorator, but output keeps track of what decorated it R = foreignDecorator(func) # apply foreignDecorator, like call to foreignDecorator(method) would have done R.decorator = newDecorator # keep track of decorator #R.original = func # might as well keep track of everything! return R newDecorator.__name__ = foreignDecorator.__name__ newDecorator.__doc__ = foreignDecorator.__doc__ # (*)We can be somewhat "hygienic", but newDecorator still isn''t signature-preserving, i.e. you will not be able to get a runtime list of parameters. For that, you need hackish libraries...but in this case, the only argument is func, so it''s not a big issue return newDecorator

Demostración para @decorator :

deco = makeRegisteringDecorator(deco) class Test2(object): @deco def method(self): pass @deco2() def method2(self): pass def methodsWithDecorator(cls, decorator): """ Returns all methods in CLS with DECORATOR as the outermost decorator. DECORATOR must be a "registering decorator"; one can make any decorator "registering" via the makeRegisteringDecorator function. """ for maybeDecorated in cls.__dict__.values(): if hasattr(maybeDecorated, ''decorator''): if maybeDecorated.decorator == decorator: print(maybeDecorated) yield maybeDecorated

¡Funciona!:

>>> print(list( methodsWithDecorator(Test2, deco) )) [<function method at 0x7d62f8>]

Sin embargo, un "decorador registrado" debe ser el decorador externo , de lo contrario la anotación del atributo .decorator se perderá. Por ejemplo, en un tren de

@decoOutermost @deco @decoInnermost def func(): ...

solo puede ver los metadatos que expone decoOutermost , a menos que tengamos referencias a los contenedores "más internos".

nota al margen: el método anterior también puede crear un .decorator que realiza un seguimiento de toda la pila de decoradores aplicados y funciones de entrada y argumentos decorador-fábrica . =) Por ejemplo, si considera la línea R.original = func , es posible utilizar un método como este para realizar un seguimiento de todas las capas de envoltura. Personalmente, esto es lo que haría si escribiera una biblioteca decoradora, porque permite una profunda introspección.

También hay una diferencia entre @foo y @bar(...) . Si bien ambos son "expressons del decorador" tal como se definen en la especificación, tenga en cuenta que foo es un decorador, mientras que la bar(...) devuelve un decorador creado dinámicamente, que luego se aplica. Por lo tanto, necesitaría una función separada makeRegisteringDecoratorFactory , que es algo así como makeRegisteringDecorator pero incluso MORE META:

def makeRegisteringDecoratorFactory(foreignDecoratorFactory): def newDecoratorFactory(*args, **kw): oldGeneratedDecorator = foreignDecoratorFactory(*args, **kw) def newGeneratedDecorator(func): modifiedFunc = oldGeneratedDecorator(func) modifiedFunc.decorator = newDecoratorFactory # keep track of decorator return modifiedFunc return newGeneratedDecorator newDecoratorFactory.__name__ = foreignDecoratorFactory.__name__ newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__ return newDecoratorFactory

Demostración para @decorator(...) :

def deco2(): def simpleDeco(func): return func return simpleDeco deco2 = makeRegisteringDecoratorFactory(deco2) print(deco2.__name__) # RESULT: ''deco2'' @deco2() def f(): pass

Este envoltorio de fábrica también funciona:

>>> print(f.decorator) <function deco2 at 0x6a6408>

bonus Incluso intentemos lo siguiente con el Método n. ° 3:

def getDecorator(): # let''s do some dispatching! return deco class Test3(object): @getDecorator() def method(self): pass @deco2() def method2(self): pass

Resultado:

>>> print(list( methodsWithDecorator(Test3, deco) )) [<function method at 0x7d62f8>]

Como puede ver, a diferencia de method2, @deco se reconoce correctamente aunque nunca se haya escrito explícitamente en la clase. A diferencia del método2, esto también funcionará si el método se agrega en tiempo de ejecución (manualmente, a través de una metaclase, etc.) o se hereda.

Tenga en cuenta que también puede decorar una clase, de modo que si "ilumina" un decorador que se utiliza para decorar métodos y clases, y luego escribe una clase dentro del cuerpo de la clase que desea analizar , entonces los methodsWithDecorator devolverán las clases decoradas así como también métodos decorados. Se podría considerar esto una característica, pero se puede escribir fácilmente la lógica para ignorarlos examinando el argumento al decorador, es decir, .original , para lograr la semántica deseada.


Para ampliar la excelente respuesta de @ ninjagecko en el Método 2: Análisis de código fuente, puede usar el módulo ast presentado en Python 2.6 para realizar la autoinspección siempre que el módulo de inspección tenga acceso al código fuente.

def findDecorators(target): import ast, inspect res = {} def visit_FunctionDef(node): res[node.name] = [ast.dump(e) for e in node.decorator_list] V = ast.NodeVisitor() V.visit_FunctionDef = visit_FunctionDef V.visit(compile(inspect.getsource(target), ''?'', ''exec'', ast.PyCF_ONLY_AST)) return res

Agregué un método decorado un poco más complicado:

@x.y.decorator2 def method_d(self, t=5): pass

Resultados:

> findDecorators(A) {''method_a'': [], ''method_b'': ["Name(id=''decorator1'', ctx=Load())"], ''method_c'': ["Name(id=''decorator2'', ctx=Load())"], ''method_d'': ["Attribute(value=Attribute(value=Name(id=''x'', ctx=Load()), attr=''y'', ctx=Load()), attr=''decorator2'', ctx=Load())"]}


Tal vez, si los decoradores no son demasiado complejos (pero no sé si hay una forma menos hacky).

def decorator1(f): def new_f(): print "Entering decorator1", f.__name__ f() new_f.__name__ = f.__name__ return new_f def decorator2(f): def new_f(): print "Entering decorator2", f.__name__ f() new_f.__name__ = f.__name__ return new_f class A(): def method_a(self): pass @decorator1 def method_b(self, b): pass @decorator2 def method_c(self, t=5): pass print A.method_a.im_func.func_code.co_firstlineno print A.method_b.im_func.func_code.co_firstlineno print A.method_c.im_func.func_code.co_firstlineno