Wywołanie metody statycznej klasy w treści klasy?


159

Kiedy próbuję użyć metody statycznej z treści klasy i zdefiniować metodę statyczną za pomocą funkcji wbudowanej staticmethodjako dekoratora, na przykład:

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

Otrzymuję następujący błąd:

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

Rozumiem, dlaczego tak się dzieje (wiązanie deskryptora) i mogę to obejść, ręcznie konwertując _stat_func()na metodę statyczną po jej ostatnim użyciu, na przykład:

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

Więc moje pytanie brzmi:

Czy są lepsze, jak w czystszym lub bardziej „Pythonic”, sposoby osiągnięcia tego celu?


4
Jeśli pytasz o Pythonicity, standardową radą jest w ogóle nie używać staticmethod. Zwykle są bardziej przydatne jako funkcje na poziomie modułu, w takim przypadku twój problem nie jest problemem. classmethod, z drugiej strony ...
Benjamin Hodgson

1
@poorsod: Tak, znam tę alternatywę. Jednak w rzeczywistym kodzie, w którym napotkałem ten problem, uczynienie funkcji metodą statyczną zamiast umieszczania jej na poziomie modułu ma więcej sensu niż w prostym przykładzie użytym w moim pytaniu.
martineau

Odpowiedzi:


179

staticmethodobiekty najwyraźniej mają __func__atrybut przechowujący oryginalną funkcję surową (ma sens, że musiały). Więc to zadziała:

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

Na marginesie, chociaż podejrzewałem, że obiekt metody static ma jakiś atrybut przechowujący oryginalną funkcję, nie miałem pojęcia o szczegółach. W duchu uczenia kogoś, jak łowić, zamiast dawać mu rybę, oto co zrobiłem, aby to zbadać i dowiedzieć się (C&P z mojej sesji w Pythonie):

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

Podobne rodzaje przekopywania się podczas sesji interaktywnej ( dirjest to bardzo pomocne) często mogą bardzo szybko rozwiązać tego rodzaju pytania.


Dobra aktualizacja ... Właśnie chciałem zapytać, skąd o tym wiesz, skoro nie widzę tego w dokumentacji - co trochę mnie denerwuje, ponieważ może to być „szczegół implementacji”.
martineau

Po dalszym przeczytaniu widzę, że __func__to tylko inna nazwa dla im_funcPy 2.6, która została dodana do Pythona 3 w celu zapewnienia zgodności w przód.
martineau

2
Rozumiem, więc technicznie jest to nieudokumentowane w tym kontekście.
martineau

1
@AkshayHazari __func__Atrybut metody statycznej zapewnia odniesienie do oryginalnej funkcji, dokładnie tak, jakbyś nigdy nie używał staticmethoddekoratora. Więc jeśli twoja funkcja wymaga argumentów, będziesz musiał je przekazać podczas wywoływania __func__. Komunikat o błędzie brzmi tak, jakbyś nie podał żadnych argumentów. Jeśli przykład stat_funcw tym poście przyjął dwa argumenty, _ANS = stat_func.__func__(arg1, arg2)
Ben

1
@AkshayHazari Nie jestem pewien, czy rozumiem. Mogą to być zmienne, po prostu muszą być zmiennymi w zakresie: albo zdefiniowane wcześniej w tym samym zakresie, w którym zdefiniowana jest klasa (często globalna), albo zdefiniowane wcześniej w zakresie klasy ( stat_funcsama jest taką zmienną). Czy chodziło Ci o to, że nie możesz używać atrybutów instancji klasy, którą definiujesz? To prawda, ale nic dziwnego; nie mamy instancji tej klasy, ponieważ wciąż definiujemy klasę! Ale tak czy inaczej, miałem na myśli ich tylko jako odpowiedniki wszelkich argumentów, które chciałeś przekazać; można tam użyć literałów.
Ben

24

Tak wolę:

class Klass(object):

    @staticmethod
    def stat_func():
        return 42

    _ANS = stat_func.__func__()

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

Wolę to rozwiązanie niż Klass.stat_funcze względu na zasadę DRY . Przypomina mi powód, dla którego pojawił się nowysuper() w Pythonie 3 :)

Ale zgadzam się z innymi, zwykle najlepszym wyborem jest zdefiniowanie funkcji na poziomie modułu.

Na przykład w przypadku @staticmethodfunkcji rekurencja może nie wyglądać zbyt dobrze (musiałbyś złamać zasadę DRY, dzwoniąc do Klass.stat_funcśrodka Klass.stat_func). Dzieje się tak, ponieważ nie masz odwołania do selfwewnętrznej metody statycznej. Dzięki funkcji na poziomie modułu wszystko będzie wyglądać dobrze.


Chociaż zgadzam się, że stosowanie self.__class__.stat_func()w zwykłych metodach ma zalety (DRY i wszystko inne) w porównaniu z używaniem Klass.stat_func(), to nie było tak naprawdę tematem mojego pytania - w rzeczywistości unikałem używania tego pierwszego, aby nie zaciemnić problemu niespójności.
martineau

1
Tak naprawdę nie jest to kwestia DRY per se. self.__class__jest lepsze, ponieważ jeśli podklasa ma pierwszeństwo stat_func, Subclass.methodwywoła podklasę stat_func. Ale żeby być całkowicie szczerym, w takiej sytuacji o wiele lepiej jest po prostu użyć prawdziwej metody zamiast statycznej.
asmeurer

@asmeurer: Nie mogę użyć prawdziwej metody, ponieważ żadne instancje klasy nie zostały jeszcze utworzone - nie mogą być, ponieważ sama klasa nie została jeszcze w pełni zdefiniowana.
martineau

Chciałem sposób wywołać metodę statyczną dla mojej klasy (która jest dziedziczona; więc rodzic faktycznie ma funkcję) i wydaje się to zdecydowanie najlepsze (i nadal będzie działać, jeśli zastąpię to później).
whitey04

11

A co z wstrzyknięciem atrybutu class po definicji klasy?

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

1
Jest to podobne do moich pierwszych prób obejścia tego problemu, ale wolałbym coś, co znajdowało się w klasie ... częściowo dlatego, że dany atrybut ma wiodące podkreślenie i jest prywatny.
martineau

11

Wynika to z faktu, że metoda staticmethod jest deskryptorem i wymaga pobrania atrybutu na poziomie klasy w celu wykonania protokołu deskryptora i uzyskania prawdziwego możliwego do wywołania.

Z kodu źródłowego:

Można go wywołać w klasie (np. C.f()) Lub w instancji (np. C().f()); instancja jest ignorowana z wyjątkiem jej klasy.

Ale nie bezpośrednio z wnętrza klasy podczas jej definiowania.

Ale jak wspomniał jeden z komentatorów, to wcale nie jest projekt „Pythonic”. Zamiast tego użyj funkcji na poziomie modułu.


Jak powiedziałem w swoim pytaniu, rozumiem, dlaczego oryginalny kod nie działał. Czy możesz wyjaśnić (lub podać link do czegoś, co robi), dlaczego jest to uważane za niepythoniczne?
martineau

Metoda staticmethod wymaga, aby sam obiekt klasy działał poprawnie. Ale jeśli wywołasz to z poziomu klasy na poziomie klasy, klasa nie jest w pełni zdefiniowana w tym momencie. Więc nie możesz jeszcze się do tego odwołać. Można wywołać metodę staticmethod spoza klasy, po jej zdefiniowaniu. Ale „ Pythonic ” nie jest właściwie zdefiniowany, podobnie jak estetyka. Mogę powiedzieć, że moje pierwsze wrażenie na temat tego kodu nie było pozytywne.
Keith

Wrażenie z której wersji (lub obu)? A konkretnie dlaczego?
martineau

Mogę tylko zgadywać, jaki jest twój ostateczny cel, ale wydaje mi się, że chcesz dynamicznego atrybutu na poziomie klasy. To wygląda na zadanie dla metaklas. Ale znowu może istnieć prostszy sposób lub inny sposób spojrzenia na projekt, który eliminuje i upraszcza bez poświęcania funkcjonalności.
Keith

3
Nie, to, co chcę zrobić, jest w rzeczywistości dość proste - polega na rozłożeniu wspólnego kodu i ponownym wykorzystaniu go, zarówno do utworzenia atrybutu klasy prywatnej w czasie tworzenia klasy, jak i później w ramach jednej lub więcej metod klas. Ten wspólny kod nie ma zastosowania poza klasą, więc naturalnie chcę, aby był jej częścią. Używanie metaklasy (lub dekoratora klas) mogłoby zadziałać, ale wydaje się to przesadą w przypadku czegoś, co powinno być łatwe do zrobienia, IMHO.
martineau

8

A co z tym rozwiązaniem? Nie polega na znajomości @staticmethodimplementacji dekoratora. Klasa wewnętrzna StaticMethod odgrywa rolę kontenera statycznych funkcji inicjalizacyjnych.

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

2
+1 za kreatywność, ale nie martwię się już o używanie, __func__ponieważ jest to teraz oficjalnie udokumentowane (zobacz sekcję Inne zmiany językowe nowości w Pythonie 2.7 i jej odniesienie do wydania 5982 ). Twoje rozwiązanie jest jeszcze bardziej przenośne, ponieważ prawdopodobnie działałoby również w wersjach Pythona wcześniejszych niż 2.6 (kiedy __func__zostało po raz pierwszy wprowadzone jako synonim im_func).
martineau

To jedyne rozwiązanie, które działa z Pythonem 2.6.
benselme

@benselme: Nie mogę zweryfikować Twojego roszczenia, ponieważ nie mam zainstalowanego Pythona 2.6, ale poważne wątpliwości to jedyne ...
martineau
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.