it-swarm.com.de

So erhalten Sie alle Methoden einer Python-Klasse mit dem angegebenen Dekorator

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
66
kraiz

Methode 1: Grundlegender Registrierungsdekorateur

Ich habe diese Frage hier bereits beantwortet: Aufruf von Funktionen nach Arrayindex in Python =)


Methode 2: Analyse des Quellcodes

Wenn Sie keine Kontrolle über die class-Definition haben, was eine Interpretation dessen ist, was Sie annehmen möchten, ist dies unmöglich (ohne Codelesereflexion ), da zum Beispiel der Dekorateur ein No-Op-Dekorierer sein könnte (wie in meinem verknüpften Beispiel), der die Funktion lediglich unverändert zurückgibt. (Wenn Sie sich jedoch erlauben, die Dekorateure einzuwickeln/neu zu definieren, siehe Methode 3: Konvertieren von Decorators in "selbstbewusst", dann finden Sie eine elegante Lösung.)

Es ist ein schrecklicher schrecklicher Hack, aber Sie könnten das inspect-Modul verwenden, um den Quellcode selbst zu lesen und ihn zu analysieren. Dies funktioniert nicht in einem interaktiven Interpreter, da das Inspect-Modul den Quellcode 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']

Man beachte, dass man auf das Parsen und die Python-Syntax achten muss, z. @deco und @deco(... sind gültige Ergebnisse, aber @deco2 sollte nicht zurückgegeben werden, wenn wir lediglich nach 'deco' fragen. Wir stellen fest, dass gemäß der offiziellen Python-Syntax unter http://docs.python.org/reference/compound_stmts.html decorators folgende sind:

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

Wir atmen erleichtert auf, wenn wir uns nicht mit Fällen wie @(deco) befassen müssen. Beachten Sie jedoch, dass dies immer noch nicht wirklich von Nutzen ist, wenn Sie wirklich komplizierte Dekorateure wie @getDecorator(...) haben, z.

def getDecorator():
    return deco

Daher kann diese Strategie, mit der Sie das Beste tun können, Code zu analysieren, Fälle wie diesen nicht erkennen. Wenn Sie jedoch diese Methode verwenden, ist das, was Sie wirklich wollen, das, was in der Definition auf die Methode geschrieben wird, in diesem Fall getDecorator.

Entsprechend der Spezifikation gilt es auch, @foo1.bar2.baz3(...) als Dekorateur zu haben. Sie können diese Methode erweitern, um damit zu arbeiten. Sie können diese Methode möglicherweise auch erweitern, um mit viel Aufwand einen <function object ...> anstelle des Funktionsnamens zurückzugeben. Diese Methode ist jedoch hackig und schrecklich.


Methode 3: Konvertierer in "selbstbewusst" umwandeln

Wenn Sie keine Kontrolle über die Dekorateur-Definition haben (was eine andere Interpretation dessen ist, was Sie möchten), werden alle diese Probleme beseitigt, da Sie die Anwendung des Dekorators steuern können. So können Sie den Dekorator durch wrapping it ändern, um Ihren own - Dekorator zu erstellen und that zum Dekorieren Ihrer Funktionen zu verwenden. Lassen Sie mich das noch einmal sagen: Sie können einen Dekorateur erstellen, der den Dekorateur dekoriert, über den Sie keine Kontrolle haben, "erleuchtend", was in unserem Fall dazu führt, dass er das tut, was er zuvor getan hat, aber auch eine .decorator-Metadateneigenschaft anhängen Das Callable kehrt zurück und erlaubt Ihnen, den Überblick zu behalten "Wurde diese Funktion eingerichtet oder nicht? Lassen Sie uns function.decorator überprüfen!". Und dann können Sie die Methoden der Klasse durchlaufen und prüfen, ob der Dekorateur die entsprechende .decorator-Eigenschaft hat! =) 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.__= 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, andernfalls geht die Annotation des .decorator-Attributs verloren. Zum Beispiel in einem Zug von

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

sie können nur Metadaten sehen, die decoOutermost verfügbar machen, es sei denn, wir behalten Verweise auf "mehr innere" Wrapper.

Randnotiz: Die obige Methode kann auch einen .decorator aufbauen, der den den gesamten Stapel an angewendeten Dekoratoren und Eingabefunktionen und Dekorator-Factory-Argumenten aufzeichnet. =) Wenn Sie beispielsweise die auskommentierte Zeile R.original = func betrachten, ist es möglich, eine Methode wie diese zu verwenden, um alle Wrapper-Layer zu verfolgen. Das ist persönlich was ich tun würde, wenn ich eine Dekorateurbibliothek schreibe, weil sie eine tiefe Selbstbeobachtung erlaubt.

Es gibt auch einen Unterschied zwischen @foo und @bar(...). Beachten Sie, dass foo ein Dekorator ist, während sie beide "Dekorator-Expressons" sind, während bar(...) einen dynamisch erstellten Dekorator zurückgibt, der dann angewendet wird. Daher benötigen Sie eine separate Funktion makeRegisteringDecoratorFactory, die etwas wie makeRegisteringDecorator aber auch MEHR META ist:

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

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 Lassen Sie uns mit Methode # 3 Folgendes versuchen:

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 method2 korrekt erkannt, obwohl es nie explizit in der Klasse geschrieben wurde. Im Gegensatz zu method2 funktioniert dies auch, wenn die Methode zur Laufzeit hinzugefügt wird (manuell, über eine Metaklasse usw.) oder vererbt wird.

Beachten Sie, dass Sie auch eine Klasse dekorieren können. Wenn Sie also einen Dekorateur "erleuchten", der zum Dekorieren von Methoden und Klassen verwendet wird, und dann eine Klasse in den Körper der zu analysierenden Klasse schreiben, dann methodsWithDecorator. wird sowohl dekorierte Kurse als auch dekorierte Methoden zurückgeben. Man könnte dies als ein Merkmal betrachten, aber Sie können leicht eine Logik schreiben, um diese zu ignorieren, indem Sie das Argument an den Dekorateur prüfen, d. H. .original, um die gewünschte Semantik zu erreichen.

99
ninjagecko

Um die hervorragende Antwort von @ ninjagecko in Methode 2: Quellcode-Analyse zu erweitern, können Sie das in Python 2.6 eingeführte Modul ast verwenden, um eine Selbstinspektion durchzuführen, solange das Inspect-Modul 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 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())"]}
14
Shane Holloway

Vielleicht, wenn die Dekorateure nicht zu komplex sind (aber ich weiß nicht, ob es weniger hackig ist).

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
0
user227667

Ich möchte nicht viel hinzufügen, nur eine einfache Variante von Ninjageckos Methode 2. Sie wirkt Wunder.

Gleicher Code, aber mit 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]
0
Skovborg Jensen

Eine einfache Möglichkeit, dieses Problem zu lösen, besteht darin, dem Dekorator Code hinzuzufü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 deco decorator zu Funktionen hinzugefügt.

0
Thomas King