Duck typing to podstawowa zasada Pythona, w myśl której obiekt jest oceniany na podstawie swojego zachowania, a nie przynależności do hierarchii klas.
Termin pochodzi z powiedzenia: "Jeśli coś wygląda jak kaczka, pływa jak kaczka i kwacze jak kaczka – to znaczy, że to kaczka". W Pythonie zachowanie obiektu (jego interfejs) jest ważniejsze niż klasa, do której należy. To wdraża zasadę "duck typing" – typizację według zachowania (structural typing).
Wydaje się, że duck typing daje maksymalną elastyczność. Ale to zwiększa liczbę ukrytych błędów: program "psuje się" tylko w czasie wykonywania, jeśli obiekt nie obsługuje potrzebnego interfejsu.
Zamiast sprawdzać typ (za pomocą isinstance lub type), pisz funkcje, które próbują wywołać potrzebne metody na obiekcie, zakładając, że jeśli je obsługuje – wszystko zadziała. W ostateczności złap AttributeError lub TypeError, aby obsłużyć nieoczekiwane obiekty.
Przykład:
def kwaczenie_i_chodzenie(kaczka): kaczka.kwaczenie() kaczka.chodzenie() class Robot: def kwaczenie(self): print("Mogę kwaczeć!") def chodzenie(self): print("Idę") kwaczenie_i_chodzenie(Robot()) # wszystko zadziała!
Kluczowe cechy:
Czy można w Pythonie sprawdzić typ za pomocą isinstance i powiedzieć, że to jest właściwe dla duck typing?
Nie. Duck typing jest wręcz przeciwny sztywnej kontroli typu. Poprawniej operować zachowaniem obiektu, a nie jego rodowodem.
Czy można zrealizować duck typing za pomocą abstrakcyjnych klas bazowych (ABC)?
Częściowo. Klasy abstrakcyjne wprowadzają elementy statycznej struktury. Duck typing nie wymaga ogłaszania pokrewieństwa – musisz po prostu zrealizować potrzebne metody. Ale od Pythona 3.8 pojawił się moduł typing.Protocol, który zbliża nas do typizacji strukturalnej.
Czy duck typing może działać z metodami magicznymi (len, getitem itp.)?
Tak. Jeśli obiekt realizuje potrzebną metodę (len), można go przekazać do funkcji, np. len(obj), i będzie to działać zgodnie z duck typing.
Programista pisze funkcję, która przyjmuje wszystko z metodą run(), bez sprawdzania. W kodzie pojawia się obiekt bez tej metody – błąd występuje dopiero w czasie działania.
Zalety:
Wady:
Użycie duck typing, ale z try/except i dokumentacją interfejsu:
def uruchom_zadanie(obj): try: obj.run() except AttributeError: print("Obiekt nie obsługuje uruchamiania zadania!")
Zalety:
Wady: