Wann sollte 'Raise NotImplementedError' verwendet werden?

101

Soll es Sie und Ihr Team daran erinnern, die Klasse korrekt zu implementieren? Ich kann eine abstrakte Klasse wie diese nicht vollständig verwenden:

class RectangularRoom(object):
    def __init__(self, width, height):
        raise NotImplementedError

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

    def isTileCleaned(self, m, n):
        raise NotImplementedError
Antonio Araujo
quelle
2
Ich würde sagen: Wenn es das "Prinzip des geringsten Erstaunens" erfüllt .
MSeifert
3
Dies ist eine nützliche Frage, wie die klare Antwort von Uriel gemäß den Dokumenten und die wertschöpfende Antwort auf abstrakte Basisklassen von Jérôme belegen.
Davos
Es kann auch verwendet werden, um anzuzeigen, dass eine abgeleitete Klasse absichtlich keine abstrakte Methode für Basisklassen implementiert, die einen bidirektionalen Schutz bieten kann. Siehe unten .
Pfabri

Antworten:

77

Wie in der Dokumentation [docs] angegeben ,

In benutzerdefinierten Basisklassen sollten abstrakte Methoden diese Ausnahme auslösen, wenn sie abgeleitete Klassen zum Überschreiben der Methode benötigen oder während die Klasse entwickelt wird, um anzuzeigen, dass die tatsächliche Implementierung noch hinzugefügt werden muss.

Beachten Sie, dass der Hauptanwendungsfall dieser Fehler zwar die Angabe abstrakter Methoden ist, die für geerbte Klassen implementiert werden sollen, Sie ihn jedoch beliebig verwenden können, beispielsweise zur Angabe eines TODOMarkers.

Uriel
quelle
6
Für abstrakte Methoden bevorzuge ich die Verwendung abc(siehe meine Antwort ).
Jérôme
@ Jérôme warum nicht beide verwenden? Mit dekorieren abstractmethodund anheben lassen NotImplementedError. Dies verbietet super().method()bei Implementierungen methodin abgeleiteten Klassen.
Timgeb
@timgeg Ich habe nicht das Bedürfnis, super (). method () zu verbieten.
Jérôme
49

Wie Uriel sagt , ist es für eine Methode in einer abstrakten Klasse gedacht, die in einer untergeordneten Klasse implementiert werden sollte, aber auch zur Angabe eines TODO verwendet werden kann.

Für den ersten Anwendungsfall gibt es eine Alternative: Abstrakte Basisklassen . Diese helfen beim Erstellen abstrakter Klassen.

Hier ist ein Python 3-Beispiel:

class C(abc.ABC):
    @abstractmethod
    def my_abstract_method(self, ...):
        ...

Beim Instanziieren Cwird eine Fehlermeldung angezeigt , da diese my_abstract_methodabstrakt ist. Sie müssen es in einer untergeordneten Klasse implementieren.

TypeError: Can't instantiate abstract class C with abstract methods my_abstract_method

Unterklasse Cund implementieren my_abstract_method.

class D(C):
    def my_abstract_method(self, ...):
        ...

Jetzt können Sie instanziieren D.

C.my_abstract_methodmuss nicht leer sein. Es kann von Dusing aufgerufen werden super().

Dies hat den Vorteil, NotImplementedErrordass Sie Exceptionzur Instanziierungszeit eine explizite und nicht zur Methodenaufrufzeit erhalten.

Jérôme
quelle
2
Verfügbar auch in Python 2.6+. Einfach from abc import ABCMeta, abstractmethodund definieren Sie Ihr ABC mit __metaclass__ = ABCMeta. Docs: docs.python.org/2/library/abc.html
BoltzmannBrain
Wenn Sie dies mit einer Klasse verwenden möchten, die eine Metaklasse definiert, müssen Sie eine neue Metaklasse erstellen, die sowohl die ursprüngliche Metaklasse der Klasse als auch ABCMeta erbt.
rabbit.aaron
26

Überlegen Sie, ob es stattdessen war:

class RectangularRoom(object):
    def __init__(self, width, height):
        pass

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

und Sie unterordnen und vergessen, es zu sagen, wie isTileCleaned()oder, vielleicht wahrscheinlicher, als isTileCLeaned(). Dann erhalten Sie in Ihrem Code eine, Nonewenn Sie ihn aufrufen.

  • Erhalten Sie die gewünschte überschriebene Funktion? Definitiv nicht.
  • Ist eine Nonegültige Ausgabe? Wer weiß.
  • Ist das beabsichtigtes Verhalten? Mit ziemlicher Sicherheit nicht.
  • Wirst du einen Fehler bekommen? Es hängt davon ab, ob.

raise NotImplmentedError Kräfte Sie es implementieren, da es wird eine Ausnahme ausgelöst , wenn Sie versuchen , es zu laufen , bis Sie das tun. Dies beseitigt viele stille Fehler. Es ist ähnlich, warum eine nackte Ausnahme fast nie eine gute Idee ist : weil Menschen Fehler machen und dies sicherstellt, dass sie nicht unter den Teppich gekehrt werden.

Hinweis: Die Verwendung einer abstrakten Basisklasse ist, wie bereits in anderen Antworten erwähnt, noch besser, da die Fehler vorab geladen werden und das Programm erst ausgeführt wird, wenn Sie sie implementieren (mit NotImplementedError wird nur dann eine Ausnahme ausgelöst, wenn sie tatsächlich aufgerufen wird).

TemporalWolf
quelle
11

Man könnte auch einen tun raise NotImplementedError() innerhalb des Kindes Methode einer @abstractmethod-Dekoriert Basisklassenmethode.


Stellen Sie sich vor, Sie schreiben ein Steuerungsskript für eine Familie von Messmodulen (physische Geräte). Die Funktionalität jedes Moduls ist eng definiert und implementiert nur eine spezielle Funktion: Eine kann ein Array von Relais sein, eine andere ein Mehrkanal-DAC oder ADC, eine andere ein Amperemeter usw.

Ein Großteil der verwendeten Befehle auf niedriger Ebene würde von den Modulen gemeinsam genutzt, um beispielsweise ihre ID-Nummern zu lesen oder einen Befehl an sie zu senden. Mal sehen, was wir an dieser Stelle haben:

Basisklasse

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

Geteilte Verben

Wir erkennen dann, dass ein Großteil der modulspezifischen Befehlsverben und damit auch die Logik ihrer Schnittstellen geteilt wird. Hier sind 3 verschiedene Verben, deren Bedeutung unter Berücksichtigung einer Reihe von Zielmodulen selbsterklärend wäre.

  • get(channel)

  • Relais: Schaltet den Ein / Aus-Status des Relais einchannel

  • DAC: Schalten Sie die Ausgangsspannung einchannel

  • ADC: Schalten Sie die Eingangsspannung einchannel

  • enable(channel)

  • Relais: Aktivieren Sie die Verwendung des Relais anchannel

  • DAC: Aktivieren Sie die Verwendung des Ausgangskanals einchannel

  • ADC: Aktivieren Sie die Verwendung des Eingangskanals einchannel

  • set(channel)

  • Relais: Relais channelein- / ausschalten

  • DAC: Schalten Sie die Ausgangsspannung einchannel

  • ADC: hmm ... mir fällt nichts Logisches ein .


Geteilte Verben werden zu erzwungenen Verben

Ich würde argumentieren, dass es ein starkes Argument dafür gibt, dass die oben genannten Verben über die Module hinweg geteilt werden, da wir gesehen haben, dass ihre Bedeutung für jedes einzelne von ihnen offensichtlich ist. Ich würde meine Basisklasse Genericso weiter schreiben :

class Generic(ABC):  # ...continued
    
    @abstractmethod
    def get(self, channel):
        pass

    @abstractmethod
    def enable(self, channel):
        pass

    @abstractmethod
    def set(self, channel):
        pass

Unterklassen

Wir wissen jetzt, dass unsere Unterklassen alle diese Methoden definieren müssen. Mal sehen, wie es für das ADC-Modul aussehen könnte:

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'

Sie fragen sich jetzt vielleicht:

Dies funktioniert jedoch nicht für das ADC- Modul, da setes dort keinen Sinn ergibt, wie wir dies oben gesehen haben!

Sie haben Recht: Nicht zu implementieren setist keine Option, da Python dann den folgenden Fehler auslösen würde, wenn Sie versuchen, Ihr ADC-Objekt zu instanziieren.

TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'

Sie müssen also etwas implementieren, da wir setein erzwungenes Verb (auch bekannt als '@abstractmethod') erstellt haben, das von zwei anderen Modulen gemeinsam genutzt wird. Gleichzeitig dürfen Sie jedoch auch nichts implementieren, was setfür dieses bestimmte Modul keinen Sinn ergibt.

NotImplementedError zur Rettung

Wenn Sie die ADC-Klasse wie folgt abschließen:

class ADC(Generic): # ...continued

    def set(self, channel):
        raise NotImplementedError("Can't use 'set' on an ADC!")

Sie machen drei sehr gute Dinge gleichzeitig:

  1. Sie schützen einen Benutzer davor, fälschlicherweise einen Befehl ('set') auszugeben, der für dieses Modul nicht implementiert ist (und nicht implementiert werden sollte!).
  2. Sie sagen ihnen explizit, wo das Problem liegt (siehe TemporalWolfs Link zu 'Nackte Ausnahmen', warum dies wichtig ist)
  3. Sie schützen die Implementierung aller anderen Module, für die die erzwungenen Verben sinnvoll sind. Dh Sie sicher , dass diese Module , für die diese Verben tun sinnvoll , diese Methoden implementieren und dass sie tun werden , so mit genau diese Verben und nicht einige andere Ad-hoc - Namen.
pfabri
quelle
-1

Vielleicht möchten Sie den @propertyDekorateur verwenden,

>>> 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
Simeon Aleksov
quelle
13
Ich sehe nicht, wie dies die Frage anspricht, wann es zu verwenden ist .
TemporalWolf