Krótka wersja
ABC oferują wyższy poziom kontraktu semantycznego między klientami a wdrożonymi klasami.
Długa wersja
Istnieje umowa między klasą a jej rozmówcami. Klasa obiecuje robić pewne rzeczy i mieć określone właściwości.
Kontrakt ma różne poziomy.
Na bardzo niskim poziomie umowa może zawierać nazwę metody lub jej liczbę parametrów.
W języku o statycznym typie umowa faktycznie byłaby egzekwowana przez kompilator. W Pythonie możesz użyć EAFP lub wpisać introspekcję, aby potwierdzić, że nieznany obiekt spełnia ten oczekiwany kontrakt.
Ale w kontrakcie są również obietnice semantyczne wyższego poziomu.
Na przykład, jeśli istnieje __str__()
metoda, oczekuje się, że zwróci ciąg znaków reprezentujący obiekt. To mogłoby usunąć całą zawartość obiektu, zatwierdzić transakcję i wypluć pustą stronę z drukarki ... ale jest powszechne zrozumienie tego, co należy zrobić, opisano w instrukcji Pythona.
To szczególny przypadek, w którym kontrakt semantyczny jest opisany w instrukcji. Co powinna print()
zrobić metoda? Czy powinien zapisać obiekt na drukarce, czy linię na ekranie, czy coś innego? To zależy - musisz przeczytać komentarze, aby zrozumieć pełną umowę tutaj. Fragment kodu klienta, który po prostu sprawdza, czy print()
metoda istnieje, potwierdził część umowy - że można wywołać metodę, ale nie zgadza się co do semantyki wyższego poziomu wywołania.
Definiowanie abstrakcyjnej klasy bazowej (ABC) jest sposobem na zawarcie umowy między implementującymi klasę a dzwoniącymi. To nie jest tylko lista nazw metod, ale wspólne zrozumienie, co te metody powinny zrobić. Jeśli odziedziczysz po tym ABC, obiecujesz przestrzegać wszystkich zasad opisanych w komentarzach, w tym semantyki print()
metody.
Pisanie kaczek w Pythonie ma wiele zalet w zakresie elastyczności w porównaniu do pisania statycznego, ale nie rozwiązuje wszystkich problemów. ABC oferuje pośrednie rozwiązanie między wolną formą Pythona a niewolą i dyscypliną języka o statycznym pisaniu.
__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.collections.Container
jest przypadkiem zdegenerowanym, który obejmuje\_\_contains\_\_
tylko i wyłącznie oznaczenie uprzednio określonej konwencji. Zgadzam się, że używanie ABC samo w sobie nie stanowi dużej wartości. Podejrzewam, że został dodany, aby umożliwić (na przykład)Set
odziedziczenie po nim. Zanim dotrzesz na miejsceSet
, nagle przynależność do ABC ma znaczną semantykę. Przedmiot nie może dwukrotnie należeć do kolekcji. NIE jest to wykrywalne przez istnienie metod.Set
jest to lepszy przykład niżprint()
. Próbowałem znaleźć nazwę metody, której znaczenie było niejednoznaczne i sama nazwa nie mogła tego uchwycić, więc nie można mieć pewności, że zrobi to dobrze tylko na podstawie nazwy i podręcznika Pythona.Set
jako przykład zamiastprint
?Set
ma sens, @Oddthinking.@ 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):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: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żnytry...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
iissubclass
, nadal nie wchodzi w MRO wirtualnej podklasy. Jest to potencjalna pułapka dla klientów: nie każdy obiekt, dla któregoisinstance(x, MyABC) == True
zdefiniowano metodyMyABC
.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.źródło
isinstance(x, collections.Iterable)
jest dla mnie bardziej zrozumiały i znam Pythona.C
podklasa usunąć (lub zepsuć nieodwracalnie)abc_method()
odziedziczoneMyABC
. Zasadnicza różnica polega na tym, że to nadklasa psuje umowę spadkową, a nie podklasę.Container.register(ContainAllTheThings)
aby dany przykład zadziałał?__subclasshook__
to „każda klasa, która spełnia ten predykat, jest uważana za podklasę do celówisinstance
iissubclass
sprawdza, niezależnie od tego, czy została zarejestrowana w ABC i niezależnie od tego, czy jest to bezpośrednia podklasa ”. Jak powiedziałem w odpowiedzi, jeśli wdrożysz odpowiedni interfejs, jesteś podklasą!Przydatną funkcją ABC jest to, że jeśli nie zaimplementujesz wszystkich niezbędnych metod (i właściwości), pojawi się błąd przy tworzeniu wystąpienia, a nie
AttributeError
, potencjalnie znacznie później, kiedy faktycznie spróbujesz użyć brakującej metody.Przykład z https://dbader.org/blog/abstract-base-classes-in-python
Edycja: w celu włączenia składni Python3, dzięki @PandasRocks
źródło
Ułatwi to ustalenie, czy obiekt obsługuje dany protokół bez konieczności sprawdzania obecności wszystkich metod w protokole lub bez wywoływania wyjątku głęboko na terytorium „wroga” z powodu braku wsparcia.
źródło
Metoda abstrakcyjna upewnij się, że każda metoda, którą wywołujesz w klasie nadrzędnej, musi pojawiać się w klasie podrzędnej. Poniżej przedstawiamy sposób wzywania i używania abstrakcji w języku noraml. Program napisany w python3
Normalny sposób dzwonienia
Metodą abstrakcyjną
Ponieważ metodatwo nie jest wywoływana w klasie potomnej, wystąpił błąd. Prawidłowe wdrożenie znajduje się poniżej
źródło