Wie bekomme ich alle Methoden einer bestimmten Klasse A, die mit dem @ decorator2 dekoriert sind?
class A():
def method_a(self):
pass
@decorator1
def method_b(self, b):
pass
@decorator2
def method_c(self, t=5):
pass
Antworten:
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.#!/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)
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(...)
zdef 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 Fall
getDecorator
.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): # 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
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ücksichtigenR.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, dassfoo
es sich um einen Dekorator handelt, währendbar(...)
ein dynamisch erstellter Dekorator zurückgegeben wird, der dann angewendet wird. Sie benötigen also eine separate FunktionmakeRegisteringDecoratorFactory
, 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 # keep track of decorator 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__) # RESULT: 'deco2' @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(): # let's do some dispatching! 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.quelle
Um die ausgezeichnete Antwort von @ ninjagecko in Methode 2: Quellcode-Analyse zu erweitern, können Sie das
ast
in Python 2.6 eingeführte Modul verwenden, um eine Selbstprüfung durchzuführen, solange das Prüfmodul Zugriff auf den Quellcode hat.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
Ich habe eine etwas kompliziertere dekorierte Methode hinzugefügt:
@x.y.decorator2 def method_d(self, t=5): pass
Ergebnisse:
> 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())"]}
quelle
Vielleicht, wenn die Dekorateure nicht zu komplex sind (aber ich weiß nicht, ob es einen weniger hackigen Weg gibt).
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
quelle
def new_f():
(die erste, Zeile 4),def new_f():
(die zweite, Zeile 11) unddef method_a(self):
. Es wird Ihnen schwer fallen, die gewünschten realen Zeilen zu finden, es sei denn, Sie haben eine Konvention, Ihre Dekorateure immer zu schreiben, indem Sie eine neue Funktion als erste Zeile definieren, und außerdem müssen Sie keine Dokumentzeichenfolgen schreiben ... obwohl Sie dies vermeiden könnten Schreiben Sie Dokumentzeichenfolgen, indem Sie eine Methode verwenden, die die Einrückung überprüft, während sie Zeile für Zeile nach oben verschoben wird, um den Namen des tatsächlichen Dekorateurs zu finden.Eine einfache Möglichkeit, dieses Problem zu lösen, besteht darin, Code in den Dekorator einzufügen, der jede übergebene Funktion / Methode einem Datensatz (z. B. einer Liste) hinzufügt.
z.B
def deco(foo): functions.append(foo) return foo
Jetzt wird jede Funktion mit dem Deko- Dekorator zu den Funktionen hinzugefügt .
quelle
Ich möchte nicht viel hinzufügen, nur eine einfache Variante von Ninjageckos Methode 2. Sie wirkt Wunder.
Gleicher Code, aber Listenverständnis anstelle eines Generators, was ich brauchte.
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]
quelle
Wenn Sie die Kontrolle über die Dekoratoren haben, können Sie Dekoratorklassen anstelle von Funktionen verwenden:
class awesome(object): def __init__(self, method): self._method = method def __call__(self, obj, *args, **kwargs): return self._method(obj, *args, **kwargs) @classmethod def methods(cls, subject): def g(): for name in dir(subject): method = getattr(subject, name) if isinstance(method, awesome): yield name, method return {name: method for name,method in g()} class Robot(object): @awesome def think(self): return 0 @awesome def walk(self): return 0 def irritate(self, other): return 0
und wenn ich es nenne
awesome.methods(Robot)
, kehrt es zurück{'think': <mymodule.awesome object at 0x000000000782EAC8>, 'walk': <mymodulel.awesome object at 0x000000000782EB00>}
quelle