Potrzebuję trochę pomocy w zrozumieniu subtelności protokołu deskryptora w Pythonie, ponieważ odnosi się on konkretnie do zachowania obiektów o rozmiarach staticmethod
. Zacznę trywialny przykład, a następnie iteracyjnie go rozwinąć, badając to zachowanie na każdym kroku:Dlaczego dekorowanie klasy łamie protokół deskryptora, zapobiegając w ten sposób zachowaniu obiektów statycznych zgodnie z oczekiwaniami?
class Stub:
@staticmethod
def do_things():
"""Call this like Stub.do_things(), with no arguments or instance."""
print "Doing things!"
W tym momencie, to zachowuje się zgodnie z oczekiwaniami, ale to, co się tutaj dzieje jest nieco subtelny: Kiedy zadzwoń pod numer Stub.do_things()
, nie wywołasz bezpośrednio instrukcji do_things. Zamiast tego, Stub.do_things
odnosi się do instancji staticmethod
, która zawinęła żądaną funkcję w swój własny protokół deskryptora, tak że faktycznie wywołujesz staticmethod.__get__
, która najpierw zwraca żądaną funkcję, a następnie , a następnie zostaje wywołana później.
>>> Stub
<class __main__.Stub at 0x...>
>>> Stub.do_things
<function do_things at 0x...>
>>> Stub.__dict__['do_things']
<staticmethod object at 0x...>
>>> Stub.do_things()
Doing things!
Jak dotąd tak dobrze. Następnie muszę zawinąć klasę w dekoratora, który będzie używany do dostosowywania klasy konkretyzacji - dekorator będzie określić, czy zezwolić na nowe dawałaby lub dostarczyć buforowane instancje:
def deco(cls):
def factory(*args, **kwargs):
# pretend there is some logic here determining
# whether to make a new instance or not
return cls(*args, **kwargs)
return factory
@deco
class Stub:
@staticmethod
def do_things():
"""Call this like Stub.do_things(), with no arguments or instance."""
print "Doing things!"
Teraz, oczywiście ta część jak jest oczekuje się, że złamie metody statyczne, ponieważ klasa jest teraz ukryta za dekoratorem, tj. Stub
nie jest klasą, ale instancją factory
, która może wywoływać instancje Stub
, gdy ją wywołasz. Rzeczywiście:
>>> Stub
<function factory at 0x...>
>>> Stub.do_things
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute 'do_things'
>>> Stub()
<__main__.Stub instance at 0x...>
>>> Stub().do_things
<function do_things at 0x...>
>>> Stub().do_things()
Doing things!
Do tej pory rozumiem, co się tutaj dzieje. Moim celem jest przywrócenie funkcji staticmethods
, aby działała tak, jak byś tego oczekiwał, nawet jeśli klasa jest opakowana. Szczęśliwie, Python stdlib zawiera coś, co nazywa się functools, co zapewnia pewne narzędzia tylko w tym celu, tj. Sprawianie, by funkcje zachowywały się bardziej jak inne funkcje, które się zawijają. Więc mogę zmienić dekorator wyglądać następująco:
def deco(cls):
@functools.wraps(cls)
def factory(*args, **kwargs):
# pretend there is some logic here determining
# whether to make a new instance or not
return cls(*args, **kwargs)
return factory
Teraz wszystko zaczyna się ciekawie:
>>> Stub
<function Stub at 0x...>
>>> Stub.do_things
<staticmethod object at 0x...>
>>> Stub.do_things()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'staticmethod' object is not callable
>>> Stub()
<__main__.Stub instance at 0x...>
>>> Stub().do_things
<function do_things at 0x...>
>>> Stub().do_things()
Doing things!
czekać .... co?functools
kopiuje staticmethod do funkcji owijania, ale nie można jej wywołać? Dlaczego nie? Za czym tęskniłem?
Grałem z tym przez chwilę i faktycznie wymyśliłem moją własną reimplementację staticmethod
, która pozwala jej funkcjonować w tej sytuacji, ale tak naprawdę nie rozumiem, dlaczego było to konieczne lub jeśli jest to nawet najlepsze rozwiązanie tego problemu. Oto pełna przykład:
class staticmethod(object):
"""Make @staticmethods play nice with decorated classes."""
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
"""Provide the expected behavior inside decorated classes."""
return self.func(*args, **kwargs)
def __get__(self, obj, objtype=None):
"""Re-implement the standard behavior for undecorated classes."""
return self.func
def deco(cls):
@functools.wraps(cls)
def factory(*args, **kwargs):
# pretend there is some logic here determining
# whether to make a new instance or not
return cls(*args, **kwargs)
return factory
@deco
class Stub:
@staticmethod
def do_things():
"""Call this like Stub.do_things(), with no arguments or instance."""
print "Doing things!"
Rzeczywiście to działa dokładnie tak, jak oczekiwano:
>>> Stub
<function Stub at 0x...>
>>> Stub.do_things
<__main__.staticmethod object at 0x...>
>>> Stub.do_things()
Doing things!
>>> Stub()
<__main__.Stub instance at 0x...>
>>> Stub().do_things
<function do_things at 0x...>
>>> Stub().do_things()
Doing things!
Jakie podejście byłoby podjąć, aby dokonać zachowywać staticmethod jak oczekiwano wewnątrz zdobione klasie? Czy to najlepszy sposób? Dlaczego wbudowany staticmethod nie implementuje na własną rękę __call__
, aby to działało bez żadnego zamieszania?
Dzięki.
Dlaczego nie stosować metody klasy? Można go nazwać w ten sam sposób, ale jest mocniejszy i (ja * myślę *) bardziej zgodny z tym i innym metaprogramowaniem. – delnan
Zdarzyło mi się używać 'classmethod's zamiast tego, jednak nie można ich wywoływać (co oznacza, że jest to dokładnie ten sam problem, jaki aktualnie mam), a także dlatego, że w pewnym sensie podoba mi się wyraźne odwoływanie się do' Stub.class_attribute 'wewnątrz' staticmethod' zamiast 'cls.class_attribute' wewnątrz' classmethod'. – robru