2009-02-23 10 views
5

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).
+0

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 –

+0

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

Odpowiedz

2

Jeśli kod biblioteki, od której jesteś zależny, używa isinstance i opiera się na dziedziczeniu, dlaczego nie zastosować tej trasy? Jeśli nie możesz zmienić biblioteki, najlepiej jest pozostać w zgodzie z nią.

Też myślę, że są uzasadnione zastosowania dla isinstance, a wraz z wprowadzeniem abstract base classes w 2.6 zostało to oficjalnie potwierdzone. Zdarzają się sytuacje, w których naprawdę dobrym rozwiązaniem jest isinstance, w przeciwieństwie do pisania na maszynie przy użyciu hasattr lub wyjątków.

Niektóre brudne opcje, jeśli z jakiegoś powodu naprawdę nie chcesz używać dziedziczenia:

  • Można zmodyfikować jedynie instancje klasy metodami instancji. Za pomocą new.instancemethod tworzysz metody opakowujące dla instancji, która następnie wywołuje oryginalną metodę zdefiniowaną w oryginalnej klasie. Wydaje się, że jest to jedyna opcja, która nie modyfikuje oryginalnej klasy ani nie definiuje nowych klas.

Jeśli można zmodyfikować klasę w czasie wykonywania istnieje wiele opcji:

  • Użyj mixin wykonania, to znaczy po prostu dodać klasę do atrybutu klasy __base__. Ale to jest bardziej używane do dodawania specyficznej funkcjonalności, a nie do bezkrytycznego zawijania, gdzie nie wiesz, co trzeba opakować.

  • Opcje w odpowiedzi Dave'a (konstruktory klas w Pythonie> = 2.6 lub Metaclasses).

Edycja: Dla konkretnego przykładu, domyślnie działa tylko pierwsza opcja. Ale nadal rozważałbym alternatywę stworzenia LogFoo lub wybrania zupełnie innego rozwiązania dla czegoś specyficznego, jak rejestrowanie.

0

Moim pierwszym impulsem byłoby próba naprawienia obraźliwego kodu, który używa isinstance. W przeciwnym razie po prostu propagujesz błędy projektowe we własnym projekcie. Każdy powód, dla którego nie możesz go zmodyfikować?

Edit: Więc uzasadnienie, że piszesz kod ramy/biblioteki, które chcesz, aby móc korzystać we wszystkich przypadkach, nawet jeśli chcą korzystać isinstance?

Myślę, że jest kilka rzeczy złego w tym:

  • starasz się wspierać złamaną paradygmat
  • Jesteś jedynym definiowania biblioteka i jego interfejsów, to jest do użytkowników do korzystania to właściwie.
  • Nie sposób można ewentualnie przewidzieć wszystkie złe programowanie użytkownicy biblioteki zrobi, więc jest to dość dużo daremny wysiłek, aby starać się wspierać złych praktyk programowania

myślę, że jesteś najlepiej wyłączyć pisanie idiomatyczne, dobrze zaprojektowany kod. Dobry kod (i zły kod) ma tendencję do rozprzestrzeniania się, więc zrób z niego przykład. Mam nadzieję, że doprowadzi to do ogólnego wzrostu jakości kodu. Przejście w drugą stronę spowoduje dalszy spadek jakości.

+0

Założeniem jest, że isinstance jest zawsze złym pomysłem. Istnieją ważne czasy, aby użyć isinstance. –

1

Trzeba pamiętać o tym, że nie trzeba koniecznie używaćw klasie bazowej, jeśli idzie się drogą dziedziczenia. Można utworzyć klasę pośrednią, która dziedziczy po tym, co nie dodaje żadnej konkretnej implementacji.Zrobiłem coś takiego kilka razy:

class Message(object): 
    pass 

class ClassToBeWrapped(object): 
    #... 

class MessageWithConcreteImplementation(Message): 
    def __init__(self): 
     self.x = ClassToBeWrapped() 
    #... add concrete implementation here 

x = MessageWithConcreteImplementation() 
isinstance(x, Message) 

Jeśli trzeba dziedziczą z innymi, przypuszczam, że można napotkać pewne problemy z wielokrotne dziedziczenie, ale to powinno być dość minimalne, jeśli nie zapewniają jakiekolwiek konkretne wdrożenie.

Jeden problem, że mam napotkasz jest to, że kiedy mam do współpracy z kodem, który wykorzystuje isinstance anty-wzorzec

Zgadzam się, że isinstance należy unikać, jeśli to możliwe, ale I” nie jestem pewien, czy nazwałbym to antypaplanetem. Istnieje kilka ważnych powodów, aby używać isinstance. Na przykład, istnieją pewne przekazy przekazujące komunikaty, które wykorzystują to do definiowania komunikatów. Na przykład, jeśli otrzymasz klasę dziedziczącą po zamknięciu systemu, czas na zamknięcie podsystemu.

0

Jeśli piszesz framework, który musi akceptować jakieś dane wejściowe od użytkowników interfejsu API, to nie ma powodu, dla którego mógłbym skorzystać z isinstance. Brzydki jak to może być, zawsze po prostu sprawdzić, czy to faktycznie zapewnia interfejs Mam zamiar użyć:

def foo(bar): 
    if hasattr(bar, "baz") and hasattr(bar, "quux"): 
     twiddle(bar.baz, bar.quux()) 
    elif hasattr(bar, "quuux"): 
     etc... 

I ja również często zapewniają piękny klasy dziedziczą domyślną funkcjonalność, jeśli użytkownik API chce wykorzystać to:

class Bar: 
    def baz(self): 
     return self.quux("glu") 

    def quux(self): 
     raise NotImplemented 
Powiązane problemy