Methode 1: Grundlegende Registrierung Dekorateur
Diese Frage habe ich hier bereits beantwortet: Funktionen über Array-Index in Python aufrufen =)
Methode 2: Analyse des Quellcodes
Wenn Sie keine Kontrolle über das haben Klasse Definition , was eine Interpretation das , was man glauben mag, dann ist dies nicht möglich (ohne Code-Lese-Reflexion), da zum Beispiel könnte der Dekorateur ein No-op Dekorateur sein (wie in meinem verknüpften Beispiel), das lediglich die unveränderte Funktion zurückgibt. (Wenn Sie sich jedoch erlauben, die Dekorateure zu verpacken / neu zu definieren, siehe Methode 3: Konvertieren von Dekorateuren in "selbstbewusst" , finden Sie eine elegante Lösung.)
Es ist ein schrecklich schrecklicher Hack, aber Sie könnten das inspect
Modul verwenden, um den Quellcode selbst zu lesen und zu analysieren. Dies funktioniert in einem interaktiven Interpreter nicht, da das Inspect-Modul die Angabe des Quellcodes im interaktiven Modus verweigert. Nachfolgend finden Sie jedoch einen Proof of Concept.
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:
nextLine = sourcelines[i+1]
name = nextLine.split('def')[1].split('(')[0].strip()
yield(name)
Es klappt!:
>>> print(list( methodsWithDecorator(Test, 'deco') ))
['method']
Beachten Sie, dass man auf das Parsen und die Python-Syntax achten muss, z. B. @deco
und @deco(...
gültige Ergebnisse sind, aber @deco2
nicht zurückgegeben werden sollten, wenn wir nur danach fragen 'deco'
. Wir stellen fest, dass gemäß der offiziellen Python-Syntax unter http://docs.python.org/reference/compound_stmts.html die Dekoratoren wie folgt lauten:
decorator ::= "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
Wir atmen erleichtert auf, wenn wir uns nicht mit Fällen wie befassen müssen @(deco)
. Beachten Sie jedoch, dass dies Ihnen immer noch nicht wirklich hilft, wenn Sie wirklich sehr komplizierte Dekorateure haben, wie @getDecorator(...)
z
def getDecorator():
return deco
Daher kann diese Best-That-You-Do-Strategie zum Parsen von Code solche Fälle nicht erkennen. Wenn Sie diese Methode verwenden, ist das, wonach Sie wirklich suchen, was in der Definition über der Methode steht, in diesem FallgetDecorator
.
Gemäß der Spezifikation gilt es auch @foo1.bar2.baz3(...)
als Dekorateur zu haben . Sie können diese Methode erweitern, um damit zu arbeiten. Möglicherweise können Sie diese Methode auch erweitern <function object ...>
, um mit viel Aufwand einen Namen anstelle des Funktionsnamens zurückzugeben. Diese Methode ist jedoch hackisch und schrecklich.
Methode 3: Konvertierer so umwandeln, dass sie "selbstbewusst" sind
Wenn Sie keine Kontrolle über die haben Dekorateur Definition (die eine andere Interpretation ist , was Sie möchten), dann werden alle diese Probleme gehen weg , weil Sie Kontrolle darüber haben, wie der Dekorateur angewendet wird. So können Sie den Dekorateur von ändern Einwickeln es, Sie zu schaffen eigene Dekorateur, und verwenden Sie, dass Ihre Funktionen zu dekorieren. Lassen Sie mich das noch einmal sagen: Sie können einen Dekorateur erstellen, der den Dekorateur dekoriert, über den Sie keine Kontrolle haben, und ihn "erleuchten", was in unserem Fall dazu führt, dass er das tut, was er zuvor getan hat, aber auch einen.decorator
Metadateneigenschaft an den von ihm zurückgegebenen Aufruf anfügt So können Sie verfolgen, ob diese Funktion dekoriert wurde oder nicht. Überprüfen wir function.decorator!Sie können die Methoden der Klasse durchlaufen und überprüfen, ob der Dekorateur über die entsprechende .decorator
Eigenschaft verfügt! =) Wie hier gezeigt:
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):
R = foreignDecorator(func)
R.decorator = newDecorator
return R
newDecorator.__name__ = foreignDecorator.__name__
newDecorator.__doc__ = foreignDecorator.__doc__
return newDecorator
Demonstration für @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
Es klappt!:
>>> print(list( methodsWithDecorator(Test2, deco) ))
[<function method at 0x7d62f8>]
Ein "registrierter Dekorateur" muss jedoch der äußerste Dekorateur sein , da sonst die .decorator
Attributanmerkung verloren geht. Zum Beispiel in einem Zug von
@decoOutermost
@deco
@decoInnermost
def func(): ...
Sie können nur Metadaten sehen, die decoOutermost
verfügbar gemacht werden, es sei denn, wir behalten Verweise auf "innerere" Wrapper bei.
Nebenbemerkung: Mit der obigen Methode kann auch eine Methode erstellt werden .decorator
, die den gesamten Stapel der angewendeten Dekoratoren und Eingabefunktionen sowie die Argumente der Dekorateurfabrik verfolgt . =) Wenn Sie beispielsweise die auskommentierte Zeile berücksichtigen R.original = func
, können Sie mit einer solchen Methode alle Wrapper-Ebenen verfolgen. Dies ist persönlich das, was ich tun würde, wenn ich eine Dekorationsbibliothek schreiben würde, da dies eine tiefe Selbstbeobachtung ermöglicht.
Es gibt auch einen Unterschied zwischen @foo
und @bar(...)
. Beachten Sie, dass es sich bei beiden um "Dekorator-Expressons" handelt, die in der Spezifikation definiert sind. Beachten Sie jedoch, dass foo
es sich um einen Dekorator handelt, während bar(...)
ein dynamisch erstellter Dekorator zurückgegeben wird, der dann angewendet wird. Sie benötigen also eine separate Funktion makeRegisteringDecoratorFactory
, die etwas ähnlicher ist, makeRegisteringDecorator
aber noch MEHR META:
def makeRegisteringDecoratorFactory(foreignDecoratorFactory):
def newDecoratorFactory(*args, **kw):
oldGeneratedDecorator = foreignDecoratorFactory(*args, **kw)
def newGeneratedDecorator(func):
modifiedFunc = oldGeneratedDecorator(func)
modifiedFunc.decorator = newDecoratorFactory
return modifiedFunc
return newGeneratedDecorator
newDecoratorFactory.__name__ = foreignDecoratorFactory.__name__
newDecoratorFactory.__doc__ = foreignDecoratorFactory.__doc__
return newDecoratorFactory
Demonstration für @decorator(...)
:
def deco2():
def simpleDeco(func):
return func
return simpleDeco
deco2 = makeRegisteringDecoratorFactory(deco2)
print(deco2.__name__)
@deco2()
def f():
pass
Dieser Generator-Factory-Wrapper funktioniert auch:
>>> print(f.decorator)
<function deco2 at 0x6a6408>
Bonus Versuchen wir mit Methode 3 sogar Folgendes:
def getDecorator():
return deco
class Test3(object):
@getDecorator()
def method(self):
pass
@deco2()
def method2(self):
pass
Ergebnis:
>>> print(list( methodsWithDecorator(Test3, deco) ))
[<function method at 0x7d62f8>]
Wie Sie sehen, wird @deco im Gegensatz zu Methode2 korrekt erkannt, obwohl es nie explizit in der Klasse geschrieben wurde. Im Gegensatz zu Methode2 funktioniert dies auch, wenn die Methode zur Laufzeit (manuell, über eine Metaklasse usw.) hinzugefügt oder geerbt wird.
Beachten Sie, dass Sie auch eine Klasse dekorieren können. Wenn Sie also einen Dekorator "aufklären", der sowohl zum Dekorieren von Methoden als auch von Klassen verwendet wird, und dann eine Klasse in den Hauptteil der Klasse schreiben, die Sie analysieren möchten , methodsWithDecorator
werden dekorierte Klassen als zurückgegeben sowie dekorierte Methoden. Man könnte dies als eine Funktion betrachten, aber Sie können leicht Logik schreiben, um diese zu ignorieren, indem Sie das Argument an den Dekorateur untersuchen, dh .original
um die gewünschte Semantik zu erreichen.