Aufrufen der statischen Methode der Klasse innerhalb des Klassenkörpers?

159

Wenn ich versuche, eine statische Methode aus dem Hauptteil der Klasse heraus zu verwenden und die statische Methode mithilfe der integrierten staticmethodFunktion als Dekorator wie folgt zu definieren:

class Klass(object):

    @staticmethod  # use as decorator
    def _stat_func():
        return 42

    _ANS = _stat_func()  # call the staticmethod

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Ich erhalte folgende Fehlermeldung:

Traceback (most recent call last):<br>
  File "call_staticmethod.py", line 1, in <module>
    class Klass(object): 
  File "call_staticmethod.py", line 7, in Klass
    _ANS = _stat_func() 
  TypeError: 'staticmethod' object is not callable

Ich verstehe, warum dies geschieht (Deskriptorbindung) , und kann es _stat_func()umgehen, indem ich es nach seiner letzten Verwendung manuell in eine statische Methode konvertiere , wie folgt:

class Klass(object):

    def _stat_func():
        return 42

    _ANS = _stat_func()  # use the non-staticmethod version

    _stat_func = staticmethod(_stat_func)  # convert function to a static method

    def method(self):
        ret = Klass._stat_func() + Klass._ANS
        return ret

Meine Frage lautet also:

Gibt es bessere Möglichkeiten, dies zu erreichen, wie bei saubereren oder "pythonischeren"?

Martineau
quelle
4
Wenn Sie nach Pythonicity fragen, ist der Standardratschlag, überhaupt nicht zu verwenden staticmethod. Sie sind normalerweise als Funktionen auf Modulebene nützlicher. In diesem Fall ist Ihr Problem kein Problem. classmethod, auf der anderen Seite ...
Benjamin Hodgson
1
@poorsod: Ja, mir ist diese Alternative bekannt. In dem Code, in dem ich auf dieses Problem gestoßen bin, ist es jedoch sinnvoller, die Funktion zu einer statischen Methode zu machen, anstatt sie auf Modulebene zu platzieren, als in dem einfachen Beispiel, das in meiner Frage verwendet wird.
Martineau

Antworten:

179

staticmethodObjekte haben anscheinend ein __func__Attribut, das die ursprüngliche Rohfunktion speichert (macht Sinn, dass sie es mussten). Das wird also funktionieren:

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    _ANS = stat_func.__func__()  # call the staticmethod

    def method(self):
        ret = Klass.stat_func()
        return ret

Abgesehen davon hatte ich keine Ahnung von den Einzelheiten, obwohl ich vermutete, dass ein statisches Methodenobjekt eine Art Attribut hatte, in dem die ursprüngliche Funktion gespeichert war. Um jemandem das Fischen beizubringen, anstatt ihm einen Fisch zu geben, habe ich Folgendes getan, um dies zu untersuchen und herauszufinden (ein C & P aus meiner Python-Sitzung):

>>> class Foo(object):
...     @staticmethod
...     def foo():
...         return 3
...     global z
...     z = foo

>>> z
<staticmethod object at 0x0000000002E40558>
>>> Foo.foo
<function foo at 0x0000000002E3CBA8>
>>> dir(z)
['__class__', '__delattr__', '__doc__', '__format__', '__func__', '__get__', '__getattribute__', '__hash__', '__init__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> z.__func__
<function foo at 0x0000000002E3CBA8>

Ähnliche Arten des Grabens in einer interaktiven Sitzung ( dirist sehr hilfreich) können diese Art von Fragen oft sehr schnell lösen.

Ben
quelle
Gutes Update ... Ich wollte gerade fragen, woher Sie das wissen, da ich es in der Dokumentation nicht sehe - was mich etwas nervös macht, es zu verwenden, weil es möglicherweise ein "Implementierungsdetail" ist.
Martineau
Nach weiterem Lesen sehe ich, dass dies __func__nur ein anderer Name für ist im_funcund Py 2.6 für Python 3-Vorwärtskompatibilität hinzugefügt wurde.
Martineau
2
Ich verstehe, technisch gesehen ist es in diesem Zusammenhang nicht dokumentiert.
Martineau
1
@AkshayHazari Mit dem __func__Attribut einer statischen Methode erhalten Sie einen Verweis auf die ursprüngliche Funktion, genau so, als hätten Sie den staticmethodDekorator noch nie verwendet . Wenn Ihre Funktion Argumente erfordert, müssen Sie diese beim Aufruf übergeben __func__. Die Fehlermeldung, die Sie ganz klingen, als hätten Sie ihr keine Argumente gegeben. Wenn das stat_funcin diesem Beitrag Beispiel zwei Argumente nehmen würde, würden Sie verwenden_ANS = stat_func.__func__(arg1, arg2)
Ben
1
@AkshayHazari Ich bin nicht sicher, ob ich es verstehe. Sie können Variablen sein, sie müssen nur In-Scope-Variablen sein: entweder früher in demselben Bereich definiert, in dem die Klasse definiert ist (häufig global), oder früher innerhalb des Klassenbereichs definiert ( stat_funcselbst ist eine solche Variable). Meinten Sie, Sie können keine Instanzattribute der von Ihnen definierten Klasse verwenden? Das ist wahr, aber nicht überraschend; wir nicht haben eine Instanz der Klasse, da wir die Klasse immer noch sind zu definieren! Aber trotzdem habe ich sie nur als Ersatz für die Argumente gemeint, die Sie vorbringen wollten. Sie könnten dort Literale verwenden.
Ben
24

So bevorzuge ich:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

    def method(self):
        return self.__class__.stat_func() + self.__class__._ANS

Ich bevorzuge diese Lösung Klass.stat_funcwegen des DRY-Prinzips . Erinnert mich an den Grund, warum essuper() in Python 3 eine neue gibt :)

Aber ich stimme den anderen zu. Normalerweise ist es die beste Wahl, eine Funktion auf Modulebene zu definieren.

Zum Beispiel mit @staticmethodFunktion sieht die Rekursion möglicherweise nicht sehr gut aus (Sie müssten das DRY-Prinzip brechen, indem Sie Klass.stat_funcinside aufrufen Klass.stat_func). Das liegt daran, dass Sie keinen Verweis auf die selfstatische Methode haben. Mit der Funktion auf Modulebene sieht alles in Ordnung aus.

Jan Vorcak
quelle
Ich stimme zwar zu, dass die Verwendung self.__class__.stat_func()in regulären Methoden Vorteile (DRY und all das) gegenüber der Verwendung hat Klass.stat_func(), aber das war nicht wirklich das Thema meiner Frage - tatsächlich habe ich die Verwendung der ersteren vermieden, um das Problem der Inkonsistenz nicht zu trüben.
Martineau
1
Es ist nicht wirklich eine Frage von DRY per se. self.__class__ist besser, denn wenn eine Unterklasse überschreibt stat_func, Subclass.methodwerden die Unterklassen aufgerufen stat_func. Aber um ganz ehrlich zu sein, ist es in dieser Situation viel besser, nur eine echte Methode anstelle einer statischen zu verwenden.
Asmeurer
@asmeurer: Ich kann keine echte Methode verwenden, da noch keine Instanzen der Klasse erstellt wurden - dies kann nicht der Fall sein, da die Klasse selbst noch nicht einmal vollständig definiert wurde.
Martineau
Ich wollte eine Möglichkeit, die statische Methode für meine Klasse aufzurufen (die vererbt wird; das übergeordnete Element hat also tatsächlich die Funktion), und dies scheint bei weitem am besten zu sein (und funktioniert immer noch, wenn ich sie später überschreibe).
whitey04
11

Was ist mit dem Einfügen des Klassenattributs nach der Klassendefinition?

class Klass(object):

    @staticmethod  # use as decorator
    def stat_func():
        return 42

    def method(self):
        ret = Klass.stat_func()
        return ret

Klass._ANS = Klass.stat_func()  # inject the class attribute with static method value
Pedro Romano
quelle
1
Dies ähnelt meinen ersten Versuchen, das Problem zu umgehen, aber ich würde etwas bevorzugen, das sich innerhalb der Klasse befindet ... teilweise, weil das betreffende Attribut einen führenden Unterstrich hat und privat ist.
Martineau
11

Dies liegt daran, dass staticmethod ein Deskriptor ist und ein Attributabruf auf Klassenebene erforderlich ist, um das Deskriptorprotokoll auszuüben und den wahren Aufruf zu erhalten.

Aus dem Quellcode:

Es kann entweder von der Klasse (zB genannt werden C.f()) oder auf einer Instanz (zB C().f()); Die Instanz wird bis auf ihre Klasse ignoriert.

Aber nicht direkt aus der Klasse heraus, während sie definiert wird.

Aber wie ein Kommentator erwähnte, ist dies überhaupt kein "Pythonic" -Design. Verwenden Sie stattdessen einfach eine Funktion auf Modulebene.

Keith
quelle
Wie ich in meiner Frage sagte, verstehe ich, warum der ursprüngliche Code nicht funktioniert hat. Können Sie erklären (oder einen Link zu etwas bereitstellen, das dies tut), warum es als unpythonisch angesehen wird?
Martineau
Die statische Methode erfordert, dass das Klassenobjekt selbst korrekt funktioniert. Wenn Sie es jedoch innerhalb der Klasse auf Klassenebene aufrufen, ist die Klasse zu diesem Zeitpunkt noch nicht vollständig definiert. Sie können also noch nicht darauf verweisen. Es ist in Ordnung, die statische Methode nach ihrer Definition von außerhalb der Klasse aufzurufen. Aber " Pythonic " ist eigentlich nicht genau definiert, ähnlich wie die Ästhetik. Ich kann Ihnen sagen, dass mein erster Eindruck von diesem Code nicht günstig war.
Keith
Eindruck von welcher Version (oder beiden)? Und warum gerade?
Martineau
Ich kann nur raten, was Ihr Endziel ist, aber es scheint mir, dass Sie ein dynamisches Attribut auf Klassenebene wollen. Das sieht nach einem Job für Metaklassen aus. Aber auch hier gibt es möglicherweise einen einfacheren oder einen anderen Weg, das Design zu betrachten, der eliminiert und vereinfacht, ohne die Funktionalität zu beeinträchtigen.
Keith
3
Nein, was ich tun möchte, ist eigentlich ziemlich einfach - das heißt, einen gemeinsamen Code herauszufiltern und wiederzuverwenden, sowohl um ein privates Klassenattribut zum Zeitpunkt der Klassenerstellung zu erstellen, als auch später innerhalb einer oder mehrerer Klassenmethoden. Dieser allgemeine Code hat außerhalb der Klasse keine Verwendung, daher möchte ich natürlich, dass er ein Teil davon ist. Die Verwendung einer Metaklasse (oder eines Klassendekorateurs) würde funktionieren, aber das scheint ein Overkill für etwas zu sein, das einfach zu tun sein sollte, IMHO.
Martineau
8

Was ist mit dieser Lösung? Es beruht nicht auf Kenntnissen über die @staticmethodImplementierung des Dekorateurs. Die innere Klasse StaticMethod wird als Container für statische Initialisierungsfunktionen abgespielt.

class Klass(object):

    class StaticMethod:
        @staticmethod  # use as decorator
        def _stat_func():
            return 42

    _ANS = StaticMethod._stat_func()  # call the staticmethod

    def method(self):
        ret = self.StaticMethod._stat_func() + Klass._ANS
        return ret
schatten
quelle
2
+1 für Kreativität, aber ich mache mir keine Sorgen mehr über die Verwendung, __func__da es jetzt offiziell dokumentiert ist (siehe Abschnitt Andere Sprachänderungen der Neuerungen in Python 2.7 und den Verweis auf Ausgabe 5982 ). Ihre Lösung ist noch portabler, da sie wahrscheinlich auch in Python-Versionen vor Version 2.6 funktionieren würde (als sie __func__erstmals als Synonym für eingeführt wurde im_func).
Martineau
Dies ist die einzige Lösung, die mit Python 2.6 funktioniert.
Benselme
@benselme: Ich kann Ihre Behauptung nicht überprüfen, weil ich Python 2.6 nicht installiert habe, aber ernsthafte Zweifel, es ist die einzige ...
Martineau