O pisaniu kaczek :
Wpisywaniu kaczek pomaga zwykle nie testować typów argumentów w metodach i funkcjach, polegając na dokumentacji, czytelnym kodzie i testowaniu w celu zapewnienia poprawnego użycia.
Informacje na temat sprawdzania poprawności argumentów (EAFP: Łatwiej prosić o wybaczenie niż pozwolenie). Dostosowany przykład stąd :
... uważa się za bardziej pytoniczne:
def my_method(self, key):
try:
value = self.a_dict[member]
except TypeError:
# do something else
Oznacza to, że nikt inny używający twojego kodu nie musi używać prawdziwego słownika lub podklasy - może użyć dowolnego obiektu, który implementuje interfejs mapowania.
Niestety w praktyce nie jest to takie proste. Co jeśli członek w powyższym przykładzie może być liczbą całkowitą? Liczby całkowite są niezmienne - więc używanie ich jako kluczy słownikowych jest całkowicie rozsądne. Są one jednak również używane do indeksowania obiektów typu sekwencji. Jeśli element członkowski jest liczbą całkowitą, wówczas przykład drugi może przepuścić listy i ciągi znaków, a także słowniki.
O programowaniu asertywnym :
Asercje to systematyczny sposób sprawdzania, czy wewnętrzny stan programu jest zgodny z oczekiwaniami programisty, w celu wykrycia błędów. W szczególności są one przydatne do wychwytywania fałszywych założeń poczynionych podczas pisania kodu lub nadużywania interfejsu przez innego programistę. Ponadto mogą do pewnego stopnia działać jako dokumentacja online, czyniąc założenia programisty oczywistymi. („Jawne jest lepsze niż niejawne.”)
Wspomniane pojęcia są czasami w konflikcie, więc liczę na następujące czynniki przy podejmowaniu decyzji, czy w ogóle nie przeprowadzam weryfikacji danych, nie przeprowadzam silnej weryfikacji lub używam stwierdzeń:
Silna walidacja. Przez silną walidację mam na myśli wprowadzenie niestandardowego wyjątku (
ApiError
na przykład). Jeśli moja funkcja / metoda jest częścią publicznego interfejsu API, lepiej zweryfikować argument, aby wyświetlić dobry komunikat o błędzie o nieoczekiwanym typie. Przez sprawdzenie typu nie mam na myśli tylko używaniaisinstance
, ale także tego, czy przekazywany obiekt obsługuje wymagany interfejs (pisanie kaczką). Chociaż dokumentuję interfejs API i określam oczekiwany typ, a użytkownik może nieoczekiwanie skorzystać z mojej funkcji, czuję się bezpieczniej, gdy sprawdzam założenia. Zwykle używamisinstance
i jeśli później chcę obsługiwać inne typy lub kaczki, zmieniam logikę sprawdzania poprawności.Programowanie asertywne. Jeśli mój kod jest nowy, często używam twierdzeń. Jakie są na to twoje porady? Czy później usuwasz twierdzenia z kodu?
Jeśli moja funkcja / metoda nie jest częścią API, ale przekazuje niektóre z jej argumentów do innego kodu, który nie został napisany, przestudiowany lub przetestowany przeze mnie, robię wiele twierdzeń zgodnie z wywoływanym interfejsem. Moja logika za tym - lepiej zawieść w moim kodzie, a następnie gdzieś o 10 poziomów głębiej w stosie śledzenia z niezrozumiałym błędem, który zmusza do częstego debugowania, a następnie i tak dodawania aser do mojego kodu.
Komentarze i porady dotyczące tego, kiedy używać sprawdzania poprawności typu / wartości, czy nie? Przepraszamy za najlepsze sformułowanie pytania.
Rozważmy na przykład następującą funkcję, gdzie Customer
jest model deklaratywny SQLAlchemy:
def add_customer(self, customer):
"""Save new customer into the database.
@param customer: Customer instance, whose id is None
@return: merged into global session customer
"""
# no validation here at all
# let's hope SQLAlchemy session will break if `customer` is not a model instance
customer = self.session.add(customer)
self.session.commit()
return customer
Istnieje kilka sposobów obsługi sprawdzania poprawności:
def add_customer(self, customer):
# this is an API method, so let's validate the input
if not isinstance(customer, Customer):
raise ApiError('Invalid type')
if customer.id is not None:
raise ApiError('id should be None')
customer = self.session.add(customer)
self.session.commit()
return customer
lub
def add_customer(self, customer):
# this is an internal method, but i want to be sure
# that it's a customer model instance
assert isinstance(customer, Customer), 'Achtung!'
assert customer.id is None
customer = self.session.add(customer)
self.session.commit()
return customer
Kiedy i dlaczego miałbyś używać każdego z nich w kontekście pisania kaczek, sprawdzania typów, sprawdzania poprawności danych?