Méthode 1 : Enregistrement de base du décorateur
J'ai déjà répondu à cette question ici : Appel de fonctions par index de tableau en Python \=)
Méthode 2 : Analyse du code source
Si vous n'avez pas le contrôle sur le classe définition qui est une interprétation de ce que vous voulez supposer, c'est impossible (sans code-reading-reflection), puisque par exemple le décorateur pourrait être un décorateur no-op (comme dans mon exemple lié) qui renvoie simplement la fonction non modifiée. (Néanmoins, si vous vous autorisez à envelopper/redéfinir les décorateurs, voir Méthode 3 : Convertir les décorateurs pour qu'ils soient "auto-conscients". alors vous trouverez une solution élégante)
C'est un terrible hack, mais vous pourriez utiliser la fonction inspect
pour lire le code source lui-même, et l'analyser. Cela ne fonctionnera pas dans un interpréteur interactif, car le module inspect refusera de donner le code source en mode interactif. Cependant, vous trouverez ci-dessous 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 que vous devez faire attention à l'analyse syntaxique et à la syntaxe python, par ex. @deco
y @deco(...
sont des résultats valables, mais @deco2
ne devrait 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 Les décorateurs sont les suivants :
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
Nous poussons un soupir de soulagement en n'ayant pas à traiter des cas tels que @(deco)
. Mais notez que cela ne vous aide toujours pas vraiment si vous avez des décorateurs vraiment très compliqués, tels que @getDecorator(...)
par exemple
def getDecorator():
return deco
Ainsi, cette stratégie d'analyse syntaxique du code, qui consiste à faire au mieux, ne peut pas détecter des cas comme celui-ci. Bien que si vous utilisez cette méthode, ce que vous recherchez réellement est ce qui est écrit au-dessus de la méthode dans la définition, ce qui dans ce cas est getDecorator
.
Selon la spécification, il est également possible d'avoir @foo1.bar2.baz3(...)
comme décorateur. Vous pouvez étendre cette méthode pour l'utiliser. Vous pouvez également étendre cette méthode pour qu'elle renvoie un objet <function object ...>
plutôt que le nom de la fonction, avec beaucoup d'efforts. Cependant, cette méthode est bidon et terrible.
Méthode 3 : Convertir les décorateurs pour qu'ils soient "auto-conscients".
Si vous n'avez pas le contrôle sur le décorateur définition (ce qui est une autre interprétation de ce que vous souhaitez), alors tous ces problèmes disparaissent car vous avez le contrôle sur la façon dont le décorateur est appliqué. Ainsi, vous pouvez modifier le décorateur en emballage pour créer votre propre et utiliser le décorateur que pour décorer vos fonctions. Permettez-moi de le répéter : vous pouvez créer un décorateur qui décore le décorateur sur lequel vous n'avez aucun contrôle, en l'"éclairant", ce qui, dans notre cas, lui fait faire ce qu'il faisait auparavant, mais également ajouter un .decorator
à l'appelant qu'il renvoie, ce qui vous permet de garder la trace de "cette fonction était-elle décorée ou non ? vérifions function.decorator !". Et puis vous pouvez itérer sur les méthodes de la classe et vérifier que le décorateur possède le code approprié. .decorator
propriété ! =) 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.__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
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 extérieur sinon le .decorator
sera perdue. Par exemple, dans un train de
@decoOutermost
@deco
@decoInnermost
def func(): ...
vous ne pouvez voir que les métadonnées qui decoOutermost
expose, à moins que nous ne conservions des références à des enveloppes "plus internes".
remarque : la méthode ci-dessus permet également de constituer un .decorator
qui garde la trace de la la pile entière des décorateurs appliqués, des fonctions d'entrée et des arguments de la 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 la trace de toutes les couches d'emballage. C'est personnellement ce que je ferais si j'écrivais une bibliothèque de décorateurs, car cela permet une introspection profonde.
Il existe également une différence entre @foo
y @bar(...)
. Bien qu'il s'agisse tous deux de "décorateurs expressifs" tels que définis dans la spécification, notez que foo
est un décorateur, tandis que bar(...)
renvoie un décorateur créé dynamiquement, qui est ensuite appliqué. Vous auriez donc besoin d'une fonction distincte makeRegisteringDecoratorFactory
c'est un peu comme makeRegisteringDecorator
mais encore plus de 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
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
Cet emballage de générateur-factory 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 voir, contrairement à la méthode2, @deco est correctement reconnue même si elle n'a jamais été explicitement écrite dans la classe. Contrairement à method2, cela fonctionnera également si la méthode est ajoutée au moment de l'exécution (manuellement, via une métaclasse, etc.) ou héritée.
Sachez que vous pouvez également décorer une classe, donc si vous "illuminez" un décorateur qui est utilisé à la fois pour décorer des méthodes et des classes, et que vous écrivez ensuite une classe dans le corps de la classe que vous voulez analyser entonces methodsWithDecorator
retournera des classes décorées ainsi que des méthodes décorées. On pourrait considérer cela comme une fonctionnalité, mais vous pouvez facilement écrire une logique pour ignorer ces éléments en examinant l'argument du décorateur, c'est-à-dire .original
pour obtenir la sémantique souhaitée.
2 votes
Avez-vous un contrôle sur le code source de "decorator2" ?
11 votes
Disons que non, juste pour garder l'intérêt. mais quand cela rend la solution beaucoup plus facile, cette solution m'intéresse aussi.
17 votes
+1 : "garder l'intérêt" : apprendre davantage de cette façon
11 votes
@S.Lott : Apprendre moins par la recherche, vous voulez dire. Regardez la première réponse ci-dessous. N'est-ce pas une très bonne contribution à l'OS, augmentant sa valeur en tant que ressource pour les programmeurs ? Je soutiens que la raison principale pourquoi cette réponse est si bonne, c'est que @kraiz voulait "garder l'intérêt". Les réponses à votre question en lien ne contiennent pas de dixième des informations contenues dans la réponse ci-dessous, sauf si vous comptez les deux liens qui ramènent ici.