@ Odpowiedź Oddthinkinga nie jest zła, ale myślę, że brakuje prawdziwego , praktycznego powodu , dla którego Python ma ABC w świecie pisania kaczych liter.
Metody abstrakcyjne są zgrabne, ale moim zdaniem tak naprawdę nie wypełniają żadnych przypadków użycia, które nie są jeszcze objęte pisaniem kaczek. Rzeczywista moc abstrakcyjnych klas bazowych polega na tym, że pozwalają one dostosować zachowanie isinstanceiissubclass . ( __subclasshook__jest w zasadzie bardziej przyjaznym interfejsem API na Pythonie __instancecheck__i__subclasscheck__ hakach). Dostosowywanie wbudowanych konstrukcji do pracy na niestandardowych typach jest w dużej mierze częścią filozofii Pythona.
Kod źródłowy Pythona jest przykładowy. Oto jak collections.Containerjest zdefiniowane w standardowej bibliotece (w momencie pisania):
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Ta definicja __subclasshook__mówi, że każda klasa z __contains__atrybutem jest uważana za podklasę kontenera, nawet jeśli nie podklasuje jej bezpośrednio. Więc mogę napisać to:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
Innymi słowy, jeśli zaimplementujesz odpowiedni interfejs, jesteś podklasą! ABC zapewniają formalny sposób definiowania interfejsów w Pythonie, pozostając wiernym duchowi pisania kaczego. Poza tym działa to w sposób zgodny z zasadą otwartego i zamkniętego .
Model obiektowy Pythona wygląda powierzchownie podobnie do bardziej „tradycyjnego” systemu OO (przez co mam na myśli Javę *) - mamy twoje klasy, twoje obiekty, twoje metody - ale kiedy zarysujesz powierzchnię, znajdziesz coś znacznie bogatszego i bardziej elastyczne. Podobnie, pojęcie abstrakcyjnych klas podstawowych w Pythonie może być rozpoznawalne przez programistę Java, ale w praktyce są one przeznaczone do zupełnie innych celów.
Czasami piszę funkcje polimorficzne, które mogą oddziaływać na pojedynczy element lub zbiór elementów, i okazuje się, isinstance(x, collections.Iterable)że jest dużo bardziej czytelny niż hasattr(x, '__iter__')lub równoważny try...exceptblok. (Gdybyś nie znał Pythona, która z tych trzech sprawiłaby, że zamiar kodu byłby najwyraźniejszy?)
To powiedziawszy, uważam, że rzadko muszę pisać własne ABC i zwykle odkrywam potrzebę takiego poprzez refaktoryzację. Jeśli widzę funkcję polimorficzną wykonującą wiele kontroli atrybutów lub wiele funkcji wykonujących te same kontrole atrybutów, ten zapach sugeruje istnienie ABC oczekującego na wyodrębnienie.
* bez wdawania się w debatę na temat tego, czy Java jest „tradycyjnym” systemem OO ...
Dodatek : Mimo że abstrakcyjna klasa podstawowa może zastąpić zachowanie isinstancei issubclass, nadal nie wchodzi w MRO wirtualnej podklasy. Jest to potencjalna pułapka dla klientów: nie każdy obiekt, dla którego isinstance(x, MyABC) == Truezdefiniowano metody MyABC.
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
Niestety, jedna z tych pułapek „po prostu nie rób tego” (których Python ma stosunkowo niewiele!): Unikaj definiowania ABC zarówno za pomocą __subclasshook__metod a, jak i nieabstrakcyjnych. Co więcej, powinieneś __subclasshook__dostosować swoją definicję do zestawu metod abstrakcyjnych zdefiniowanych przez ABC.
__contains__a klasą dziedziczącącollections.Container? W twoim przykładzie w Pythonie zawsze było wspólne rozumienie__str__. Implementacja__str__składa się z takich samych obietnic, jak dziedziczenie po ABC, a następnie implementacja__str__. W obu przypadkach możesz zerwać umowę; nie ma żadnej możliwej do udowodnienia semantyki, takiej jak te, które mamy podczas pisania statycznego.