Comment obtenir toutes les méthodes d'une classe donnée A qui sont décorées avec le @ decorator2?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
J'ai déjà répondu à cette question ici: Appel de fonctions par index de tableau en Python =)
Si vous n'avez pas le contrôle sur la classe definition, qui est une interprétation de ce que vous voulez supposer, ceci est impossible (sans code lecture-réflexion ), car par exemple, le décorateur pourrait être un décorateur sans problème (comme dans mon exemple associé) qui renvoie simplement la fonction sans modification. (Néanmoins, si vous vous permettez d'envelopper/redéfinir les décorateurs, voir Méthode 3: Convertir les décorateurs pour qu'ils soient "conscients", alors vous trouverez une solution élégante.)
C'est un hack terrible, mais vous pouvez utiliser le module inspect
pour lire le code source lui-même et l'analyser. Cela ne fonctionnera pas dans un interpréteur interactif, car le module inspecter refusera de donner du code source en mode interactif. Cependant, ci-dessous est une preuve de concept.
#!/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)
Ça marche!:
>>> print(list( methodsWithDecorator(Test, 'deco') ))
['method']
Notez qu'il faut prêter attention à l'analyse syntaxique et à la syntaxe python, par ex. @deco
et @deco(...
sont des résultats valides, mais @deco2
ne doit pas être renvoyé si nous demandons simplement 'deco'
. Nous remarquons que, selon la syntaxe officielle de Python, à http://docs.python.org/reference/compound_stmts.html decorators sont les suivants:
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
Nous poussons un soupir de soulagement de ne pas avoir à traiter des cas tels que @(deco)
. Mais notez que cela ne vous aide toujours pas vraiment si vous avez des décorateurs vraiment compliqués, tels que @getDecorator(...)
, par exemple.
def getDecorator():
return deco
Ainsi, cette stratégie d’analyse de code «best-you-can-do» ne peut pas détecter de tels cas. Cependant, si vous utilisez cette méthode, ce que vous recherchez réellement est ce qui est écrit en haut de la méthode dans la définition, qui dans ce cas est getDecorator
.
Selon la spécification, il est également valide d'avoir @foo1.bar2.baz3(...)
en tant que décorateur. Vous pouvez étendre cette méthode pour travailler avec cela. Vous pourriez également être en mesure d'étendre cette méthode pour renvoyer un <function object ...>
plutôt que le nom de la fonction, avec beaucoup d'effort. Cette méthode est cependant bidon et terrible.
Si vous ne contrôlez pas la décorateur definition (ce qui est une autre interprétation de ce que vous souhaitez), toutes ces questions disparaissent, car vous avez le contrôle sur l'application du décorateur. Ainsi, vous pouvez modifier le décorateur en l'enveloppant pour créer votre décorateur propre et utiliser that pour décorer vos fonctions. Permettez-moi de le dire encore une fois: vous pouvez créer un décorateur qui décore celui-ci sur lequel vous n'avez aucun contrôle, en "l'éclairant", ce qui dans notre cas le fait faire ce qu'il faisait auparavant mais aussi ajoute une propriété de métadonnées .decorator
à l'appelable, il revient, vous permettant de garder une trace de "cette fonction a-t-elle été décorée ou non? vérifions function.decorator!". Et alors vous pouvez parcourir les méthodes de la classe et vérifier si le décorateur a la propriété .decorator
appropriée! =) Comme démontré ici:
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.__= 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
Démonstration pour @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
Ça marche!:
>>> print(list( methodsWithDecorator(Test2, deco) ))
[<function method at 0x7d62f8>]
Cependant, un "décorateur enregistré" doit être le décorateur le plus à l'extérieur, sinon l'annotation de l'attribut .decorator
sera perdue. Par exemple dans un train de
@decoOutermost
@deco
@decoInnermost
def func(): ...
vous ne pouvez voir que les métadonnées que decoOutermost
expose, sauf si nous conservons des références à des wrappers "plus internes".
note de bas de page: la méthode ci-dessus peut également créer un .decorator
qui garde la trace de la pile entière de décorateurs appliqués, de fonctions d'entrée et d'arguments de fabrique de décorateurs. =) Par exemple, si vous considérez la ligne commentée R.original = func
, il est possible d’utiliser une méthode comme celle-ci pour garder une trace de toutes les couches de l’enveloppe. Personnellement, c’est ce que je ferais si j’écrivais une bibliothèque de décorateurs, car elle permet une introspection profonde.
Il existe également une différence entre @foo
et @bar(...)
. Bien qu'ils soient tous deux des "expressions de décorateur" tels que définis dans la spécification, notez que foo
est un décorateur, alors que bar(...)
renvoie un décorateur créé de manière dynamique, qui est ensuite appliqué. Ainsi, vous auriez besoin d’une fonction distincte makeRegisteringDecoratorFactory
, qui ressemble un peu à makeRegisteringDecorator
mais encore PLUS 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.__= foreignDecoratorFactory.__name__
newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__
return newDecoratorFactory
Démonstration pour @decorator(...)
:
def deco2():
def simpleDeco(func):
return func
return simpleDeco
deco2 = makeRegisteringDecoratorFactory(deco2)
print(deco2.__name__)
# RESULT: 'deco2'
@deco2()
def f():
pass
Ce wrapper générateur-usine fonctionne également:
>>> print(f.decorator)
<function deco2 at 0x6a6408>
bonus Essayons même ce qui suit avec la méthode n ° 3:
def getDecorator(): # let's do some dispatching!
return deco
class Test3(object):
@getDecorator()
def method(self):
pass
@deco2()
def method2(self):
pass
Résultat:
>>> print(list( methodsWithDecorator(Test3, deco) ))
[<function method at 0x7d62f8>]
Comme vous pouvez le constater, contrairement à method2, @deco est correctement reconnu même s'il n'a jamais été explicitement écrit dans la classe. Contrairement à method2, cela fonctionnera également si la méthode est ajoutée à l'exécution (manuellement, via une métaclasse, etc.) ou héritée.
Sachez que vous pouvez également décorer une classe. Ainsi, si vous "éclairez" un décorateur utilisé pour décorer des méthodes et des classes, puis écrivez une classe dans le corps de la classe que vous souhaitez analyser, alors methodsWithDecorator
retournera les classes décorées ainsi que les méthodes décorées. On pourrait considérer cela comme une fonctionnalité, mais vous pouvez facilement écrire une logique pour ignorer celles-ci en examinant l'argument adressé au décorateur, c'est-à-dire .original
, pour obtenir la sémantique souhaitée.
Pour développer l'excellente réponse de @ ninjagecko à la Méthode 2: Analyse du code source, vous pouvez utiliser le module ast
introduit dans Python 2.6 pour effectuer l'auto-inspection tant que le module inspecte a accès au code source.
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
J'ai ajouté une méthode de décor légèrement plus compliquée:
@x.y.decorator2
def method_d(self, t=5): pass
Résultats:
> 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())"]}
Peut-être, si les décorateurs ne sont pas trop complexes (mais je ne sais pas s'il existe un moyen moins hacky).
def decorator1(f):
def new_f():
print "Entering decorator1", f.__name__
f()
new_f.__= f.__name__
return new_f
def decorator2(f):
def new_f():
print "Entering decorator2", f.__name__
f()
new_f.__= 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
Je ne veux pas ajouter grand chose, juste une simple variation de la Méthode 2 de ninjagecko. Cela fonctionne à merveille.
Même code, mais en utilisant la compréhension de liste au lieu d’un générateur, c’est ce dont j'avais besoin.
def methodsWithDecorator(cls, decoratorName):
sourcelines = inspect.getsourcelines(cls)[0]
return [ sourcelines[i+1].split('def')[1].split('(')[0].strip()
for i, line in enumerate(sourcelines)
if line.split('(')[0].strip() == '@'+decoratorName]
Un moyen simple de résoudre ce problème consiste à insérer dans le décorateur un code qui ajoute chaque fonction/méthode transmise à un ensemble de données (par exemple une liste).
par exemple.
def deco(foo):
functions.append(foo)
return foo
maintenant chaque fonction avec deco decorator sera ajoutée à functions .