Czy ma to przypominać sobie i swojemu zespołowi o prawidłowym wdrożeniu zajęć? Nie w pełni korzystam z takiej klasy abstrakcyjnej:
class RectangularRoom(object):
def __init__(self, width, height):
raise NotImplementedError
def cleanTileAtPosition(self, pos):
raise NotImplementedError
def isTileCleaned(self, m, n):
raise NotImplementedError
Odpowiedzi:
Zgodnie z dokumentacją [docs] ,
Zauważ, że chociaż głównym podanym przypadkiem użycia ten błąd wskazuje na abstrakcyjne metody, które powinny być zaimplementowane w klasach dziedziczonych, możesz go użyć w dowolny sposób, na przykład do wskazania
TODO
znacznika.źródło
abc
(zobacz moją odpowiedź ).abstractmethod
i pozwól mu się podnieśćNotImplementedError
. To zabraniasuper().method()
w implementacjachmethod
klas pochodnych.Jak mówi Uriel , jest on przeznaczony dla metody w klasie abstrakcyjnej, która powinna być zaimplementowana w klasie potomnej, ale może być również używana do wskazywania TODO.
Istnieje alternatywa dla pierwszego przypadku użycia: abstrakcyjne klasy bazowe . Pomagają w tworzeniu klas abstrakcyjnych.
Oto przykład Pythona 3:
class C(abc.ABC): @abc.abstractmethod def my_abstract_method(self, ...): ...
Podczas tworzenia wystąpienia
C
wystąpi błąd, ponieważmy_abstract_method
jest abstrakcyjny. Musisz zaimplementować to w klasie podrzędnej.TypeError: Can't instantiate abstract class C with abstract methods my_abstract_method
Podklasa
C
i implementacjamy_abstract_method
.class D(C): def my_abstract_method(self, ...): ...
Teraz możesz utworzyć wystąpienie
D
.C.my_abstract_method
nie musi być pusty. Można go wywołaćD
za pomocąsuper()
.Zaletą tego rozwiązania
NotImplementedError
jest to, że otrzymujesz jawną informacjęException
w czasie tworzenia instancji, a nie w czasie wywołania metody.źródło
from abc import ABCMeta, abstractmethod
zdefiniuj swoje ABC za pomocą__metaclass__ = ABCMeta
. Dokumenty: docs.python.org/2/library/abc.htmlZastanów się, czy zamiast tego było:
class RectangularRoom(object): def __init__(self, width, height): pass def cleanTileAtPosition(self, pos): pass def isTileCleaned(self, m, n): pass
a ty podklasujesz i zapominasz powiedzieć, jak to zrobić
isTileCleaned()
lub, co może bardziej prawdopodobne, wpisać to jakoisTileCLeaned()
. Następnie w swoim kodzie otrzymasz,None
gdy go wywołasz.None
dane wyjściowe są prawidłowe? Kto wie.raise NotImplmentedError
zmusza cię do zaimplementowania go, ponieważ zgłosi wyjątek, gdy spróbujesz go uruchomić, dopóki tego nie zrobisz. To usuwa wiele cichych błędów. Jest to podobne do tego, dlaczego nagie z wyjątkiem prawie nigdy nie jest dobrym pomysłem : ponieważ ludzie popełniają błędy, a to zapewnia, że nie zostaną zamiatani pod dywan.Uwaga: użycie abstrakcyjnej klasy bazowej, jak wspomniały inne odpowiedzi, jest jeszcze lepsze, ponieważ wtedy błędy są ładowane z przodu i program nie będzie działał, dopóki ich nie zaimplementujesz (z NotImplementedError, wyrzuci wyjątek tylko wtedy, gdy zostanie faktycznie wywołany).
źródło
Można również wykonać
raise NotImplementedError()
wewnętrzną metodę@abstractmethod
-dekorowaną metodę klasy bazowej.Wyobraź sobie, że piszesz skrypt sterujący dla rodziny modułów pomiarowych (urządzeń fizycznych). Funkcjonalność każdego modułu jest wąsko zdefiniowana, realizując tylko jedną dedykowaną funkcję: jednym może być szereg przekaźników, innym wielokanałowym przetwornikiem cyfrowo-analogowym lub przetwornikiem cyfrowo-analogowym, innym amperomierzowi itp.
Wiele z używanych poleceń niskiego poziomu byłoby współdzielonych między modułami, na przykład w celu odczytania ich numerów identyfikacyjnych lub wysłania do nich polecenia. Zobaczmy, co mamy w tym momencie:
Klasa podstawowa
from abc import ABC, abstractmethod #< we'll make use of these later class Generic(ABC): ''' Base class for all measurement modules. ''' # Shared functions def __init__(self): # do what you must... def _read_ID(self): # same for all the modules def _send_command(self, value): # same for all the modules
Czasowniki wspólne
Następnie zdajemy sobie sprawę, że wiele czasowników poleceń specyficznych dla modułu, a zatem logika ich interfejsów jest również współdzielona. Oto 3 różne czasowniki, których znaczenie byłoby oczywiste, biorąc pod uwagę liczbę modułów docelowych.
get(channel)
przekaźnik: uzyskaj stan włączenia / wyłączenia przekaźnika
channel
DAC: uzyskać wyjściowego napięcia na
channel
ADC: uzyskać wejściowego napięcia na
channel
enable(channel)
przekaźnik: włącz użycie przekaźnika
channel
DAC: włącz użycie kanału wyjściowego
channel
ADC: włącz używanie kanału wejściowego
channel
set(channel)
przekaźnik:
channel
włączanie / wyłączanie przekaźnikaDAC: ustawienie wyjściowe napięcie na
channel
ADC: hmm ... nic logicznego nie przychodzi na myśl.
Czasowniki współdzielone stają się czasownikami wymuszonymi
Twierdzę, że powyższe czasowniki powinny być współużytkowane przez moduły, ponieważ widzieliśmy, że ich znaczenie jest oczywiste dla każdego z nich. Kontynuowałbym pisanie mojej klasy bazowej w ten
Generic
sposób:class Generic(ABC): # ...continued @abstractmethod def get(self, channel): pass @abstractmethod def enable(self, channel): pass @abstractmethod def set(self, channel): pass
Podklasy
Teraz wiemy, że wszystkie nasze podklasy będą musiały zdefiniować te metody. Zobaczmy, jak mógłby wyglądać moduł ADC:
class ADC(Generic): def __init__(self): super().__init__() #< applies to all modules # more init code specific to the ADC module def get(self, channel): # returns the input voltage measured on the given 'channel' def enable(self, channel): # enables accessing the given 'channel'
Możesz się teraz zastanawiać:
Masz rację: brak implementacji
set
nie jest opcją, ponieważ Python wywołałby poniższy błąd podczas próby utworzenia instancji obiektu ADC.TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'
Więc trzeba zaimplementować coś, bo zrobiliśmy egzekwowane czasownik (aka „@abstractmethod”), który jest wspólny dla dwóch innych modułów, ale w tym samym czasie, należy również wdrożyć jako nic nie nie ma sensu dla tego konkretnego modułu.
set
set
NotImplementedError to the Rescue
Ukończenie klasy ADC w ten sposób:
class ADC(Generic): # ...continued def set(self, channel): raise NotImplementedError("Can't use 'set' on an ADC!")
Robisz jednocześnie trzy bardzo dobre rzeczy:
źródło
Możesz użyć
@property
dekoratora,>>> class Foo(): ... @property ... def todo(self): ... raise NotImplementedError("To be implemented") ... >>> f = Foo() >>> f.todo Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in todo NotImplementedError: To be implemented
źródło