5 Stimmen

Decorator ändert Funktionsstatus von Methode zu Funktion

[Aktualisiert]: Antwort inline unter der Frage

Ich habe ein Inspektionsprogramm und ein Ziel ist es, dass die Logik in einem Dekorator weiß, ob die Funktion, die er dekoriert, eine Klassenmethode oder eine reguläre Funktion ist. Dies schlägt auf eine seltsame Weise fehl. Unten ist der Code in Python 2.6 ausgeführt:

def decorate(f):
    print 'decorator thinks function is', f
    return f

class Test(object):
    @decorate
    def test_call(self):
        pass

if __name__ == '__main__':
    Test().test_call()
    print 'main thinks function is', Test().test_call

Dann bei der Ausführung:

decorator thinks function is <function test_call at 0x10041cd70>
main thinks function is <bound method Test.test_call of <__main__.Test object at 0x100425a90>>

Jeder Anhaltspunkt auf, was schief geht, und wenn es möglich ist, für @decorate richtig zu folgern, dass test_call eine Methode ist?

[Antwort] Die unten stehende Antwort von Carl ist nahezu perfekt. Ich hatte ein Problem bei der Verwendung des Dekorators für eine Methode, die von Unterklassen aufgerufen wird. Ich habe seinen Code so angepasst, dass er einen im_func-Vergleich für Oberklassenmitglieder enthält:

ismethod = False
for item in inspect.getmro(type(args[0])):
    for x in inspect.getmembers(item):
        if 'im_func' in dir(x[1]):
            ismethod = x[1].im_func == newf
            if ismethod:
                break
    else:
        continue
    break

5voto

carl Punkte 48262

Wie bereits erwähnt, wird eine Funktion dekoriert, bevor sie gebunden wird, so dass Sie nicht direkt feststellen können, ob es sich um eine "Methode" oder eine "Funktion" handelt.

Ein vernünftiger Weg, um festzustellen, ob eine Funktion eine Methode ist oder nicht, ist zu prüfen, ob "self" der erste Parameter ist. Obwohl dies nicht narrensicher ist, hält sich der meiste Python-Code an diese Konvention:

import inspect
ismethod = inspect.getargspec(method).args[0] == 'self'

Hier ist ein verworrener Weg, der automatisch herauszufinden scheint, ob die Methode eine Begrenzung ist oder nicht. Funktioniert für ein paar einfache Fälle auf CPython 2.6, aber keine Versprechungen. Es entscheidet eine Funktion ist eine Methode, wenn das erste Argument zu ist ein Objekt mit der dekorierten Funktion gebunden.

import inspect

def decorate(f):
    def detect(*args, **kwargs):
        try:
            members = inspect.getmembers(args[0])
            members = (x[1].im_func for x in members if 'im_func' in dir(x[1]))
            ismethod = detect in members
        except:
            ismethod = False
        print ismethod

        return f(*args, **kwargs)
    return detect

@decorate
def foo():
    pass

class bar(object):
    @decorate
    def baz(self):
        pass

foo() # prints False
bar().baz() # prints True

3voto

Katriel Punkte 115208

Nein, das ist nicht möglich, wie Sie es gewünscht haben, denn es gibt keinen inhärenten Unterschied zwischen gebundenen Methoden und Funktionen. Eine Methode ist einfach eine Funktion, die so verpackt ist, dass sie die aufrufende Instanz als erstes Argument erhält (mit Python Deskriptoren ).

Ein Anruf wie:

Test.test_call

die eine ungebundene Methode zurückgibt, wird übersetzt in

Test.__dict__[ 'test_call' ].__get__( None, spam )

die eine ungebundene Methode ist, auch wenn

Test.__dict__[ 'test_call' ]

ist eine Funktion. Das liegt daran, dass Funktionen Deskriptoren sind, deren __get__ Methoden geben Methoden zurück; wenn Python eine dieser Methoden in der Lookup-Kette sieht, ruft es die __get__ Methode, anstatt die Kette nach oben fortzusetzen.

In der Tat wird die "gebundene Methodik" einer Funktion zur Laufzeit bestimmt, nicht zur Definitionszeit!

Der Dekorateur sieht die Funktion einfach so, wie sie definiert ist, ohne sie in einer __dict__ und kann daher nicht sagen, ob es sich um eine gebundene Methode handelt.


Es könnte möglich sein, dies mit einem Klassendekorator zu tun, der die __getattribute__ aber das ist ein besonders fieser Hacker. Warum müssen Sie diese Funktionalität haben? Da Sie den Dekorator selbst auf die Funktion setzen müssen, könnten Sie ihm doch ein Argument übergeben, das angibt, ob die Funktion innerhalb einer Klasse definiert ist?

class Test:
    @decorate( method = True )
    def test_call:
        ...

@decorate( method = False )
def test_call:
    ...

1voto

Jacek Konieczny Punkte 7805

Ihr Dekorator wird ausgeführt, bevor die Funktion zu einer Methode wird. def Schlüsselwort innerhalb einer Klasse eine Funktionszeile an einer beliebigen anderen Stelle definiert, werden die im Körper einer Klasse definierten Funktionen der Klasse als Methoden hinzugefügt. Der Decorator bearbeitet die Funktion, bevor sie von der Klasse verarbeitet wird, weshalb Ihr Code "fehlschlägt".

Es gibt keine Möglichkeit für @decorate zu erkennen, dass die Funktion eigentlich eine Methode ist. Ein Workaround dafür wäre, die Funktion zu dekorieren, was auch immer sie ist (z.B. durch Hinzufügen eines Attributs do_something_about_me_if_I_am_a_method ;-) ) und dann erneut verarbeiten, nachdem die Klasse berechnet wurde (indem man über die Klassenmitglieder iteriert und mit diesen dekorierten Elementen tut, was man will).

0voto

robert Punkte 31314

Ich habe ein etwas anderes Beispiel ausprobiert, mit einer verzierten und einer unverzierten Methode.

def decorate(f):
  print 'decorator thinks function is', f
  return f

class Test(object):
  @decorate
  def test_call(self):
    pass
  def test_call_2(self):
    pass

if __name__ == '__main__':
  print 'main thinks function is', Test.test_call
  print 'main thinks function 2 is', Test.test_call_2

Dann lautet die Ausgabe:

decorator thinks function is <function test_call at 0x100426b18>
main thinks function is <unbound method Test.test_call>
main thinks function 2 is <unbound method Test.test_call_2>

So sah der Dekorator einen anderen Typ als die Hauptfunktion, aber der Dekorator änderte den Typ der Funktion nicht, sonst wäre sie anders als die undekorierte Funktion.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X