W języku Python widziałem zalecenie używania przechwytywania lub pakowania w celu rozszerzenia funkcjonalności obiektu lub klasy, a nie dziedziczenia. W szczególności, myślę, że Alex Martelli mówił o tym w swoim przemówieniu Python Design Patterns. Widziałem ten wzorzec używany w bibliotekach do wstrzykiwania zależności, np. pycontainer.Zawijanie obiektów w celu rozszerzenia/dodania funkcjonalności podczas pracy z instancjami isinstance
Jeden problem, że mam napotkasz jest to, że kiedy mam do współpracy z kodem, który wykorzystuje isinstance anti-pattern, ten wzór nie powiedzie, ponieważ obiekt wstrzymania/opakowanie nie przejdzie testu isinstance
. Jak mogę ustawić obiekt trzymający/zawijający, aby obejść niepotrzebne sprawdzanie typu? Czy można to zrobić w sposób ogólny? W pewnym sensie potrzebuję czegoś dla instancji klasy analogicznej do dekoratorów funkcji zachowujących sygnatury (np. simple_decorator lub Michele Simionato's decorator).
Kwalifikacja: Nie twierdzę, że wszystkie użycie isinstance
jest niewłaściwe; kilka odpowiedzi na to wskazuje. To powiedziawszy, należy zauważyć, że użycie isinstance
stwarza znaczne ograniczenia oddziaływań z obiektami --- wymusza dziedziczenie jako źródło polimorfizmu, a nie zachowanie.
Wydaje się, że istnieje pewne niejasności co do tego, jak/dlaczego jest to problem, więc pozwólcie, że przedstawię prosty przykład (szeroko usunięty z pycontainer). Powiedzmy, że mamy klasę Foo, a także FooFactory. Dla przykładu, załóżmy, że chcemy mieć możliwość tworzenia instancji obiektów Foo, które rejestrują każde wywołanie funkcji, lub nie - myślą AOP. Co więcej, chcemy to zrobić bez modyfikowania klasy/źródła Foo w żaden sposób (np. Możemy faktycznie wprowadzić ogólną fabrykę, która może dodać zdolność rejestrowania do dowolnej instancji klasy w locie). Pierwszy stab na to może być:
class Foo(object):
def bar():
print 'We\'re out of Red Leicester.'
class LogWrapped(object):
def __init__(self, wrapped):
self.wrapped = wrapped
def __getattr__(self, name):
attr = getattr(self.wrapped, name)
if not callable(attr):
return attr
else:
def fun(*args, **kwargs):
print 'Calling ', name
attr(*args, **kwargs)
print 'Called ', name
return fun
class FooFactory(object):
def get_foo(with_logging = False):
if not with_logging:
return Foo()
else:
return LogWrapped(Foo())
foo_fact = FooFactory()
my_foo = foo_fact.get_foo(True)
isinstance(my_foo, Foo) # False!
Istnieje maj powody, dla których warto robić rzeczy dokładnie w ten sposób (zamiast użytku dekoratorzy, etc.), ale należy pamiętać:
- My don nie chcesz dotykać klasy Foo. Załóżmy, że piszemy kod strukturalny, który może być używany przez klientów, o których jeszcze nie wiemy.
- Chodzi o to, aby zwrócić obiekt, który jest w zasadzie Foo, ale z dodatkową funkcjonalnością. Powinien wyglądać na Foo - tak bardzo, jak to możliwe - na jakikolwiek inny kod klienta oczekujący Foo. Stąd chęć do pracy około
isinstance
. - Tak, wiem, że nie potrzebuję klasy fabrycznej (zapobiegawczo bronię się tutaj).
Potrząśnij moją pięścią w bezsilnej wściekłości na isinstance. Nie możesz naprawić kodu przy pomocy isinstance? http://stackoverflow.com/questions/423823/whats-your-favorite-programmer-ignorance-pet-peeve/423857#423857 –
Bezradna furia :) W niektórych przypadkach mam dostęp do isinstance. Ale innym przypadkiem użycia byłoby chcieć napisać kod "framework" (np., Pycontainer powyżej), który jest miły dla innych. – zweiterlinde