@ 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 isinstance
iissubclass
. ( __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.Container
jest 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...except
blok. (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 isinstance
i 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) == True
zdefiniowano 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.