何时使用“ raise NotImplementedError”?


108

是为了提醒自己和您的团队正确实施课堂吗?我没有完全使用像这样的抽象类:

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

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

    def isTileCleaned(self, m, n):
        raise NotImplementedError


2
我会说:当它满足“最少惊讶的原理”时
MSeifert

3
这是一个有用的问题,Uriel根据文档给出的明确答案以及Jérôme关于抽象基类的增值答案就证明了这一点。
达沃斯

它也可以用来表示派生类没有故意实现可提供双向保护的基类抽象方法。见下文
pfabri '20年

Answers:


80

如文档所述[docs]

在用户定义的基类中,当抽象方法要求派生类重写该方法时,或者在开发类以指示仍需要添加实际实现时,应引发此异常。

请注意,尽管主要说明的用例是该错误是对应该在继承的类上实现的抽象方法的指示,但是您可以随意使用它,例如用于TODO标记的指示。


6
对于抽象方法,我更喜欢使用abc(请参阅我的回答)。
杰罗姆

@Jérôme为什么不同时使用两者?装饰abstractmethod并使其升高NotImplementedError。这禁止super().method()method派生类的实现中使用。
timgeb

@timgeg我觉得不需要禁止super()。method()。
杰罗姆

51

正如Uriel所说,它是指抽象类中的一种方法,该方法应在子类中实现,但也可以用来指示TODO。

第一个用例有一个替代方法:抽象基类。这些有助于创建抽象类。

这是一个Python 3示例:

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

实例化时C,您会收到错误消息,因为它my_abstract_method是抽象的。您需要在子类中实现它。

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

子类C和实现my_abstract_method

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

现在您可以实例化D

C.my_abstract_method不必为空。它可以被称为D使用super()

这样做的好处NotImplementedError是,您可以Exception在实例化时(而不是在方法调用时)获得显式信息。


2
同样在Python 2.6+中可用。只需用from abc import ABCMeta, abstractmethod定义您的ABC __metaclass__ = ABCMeta。文件:docs.python.org/2/library/abc.html
BoltzmannBrain

如果要与定义元类的类一起使用,则需要创建一个新的元类,该类继承该类的原始元类和ABCMeta。
—rabbit.aaron

27

考虑一下是否是:

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

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

并且您继承了子类,却忘了告诉它如何操作isTileCleaned(),或者更可能将其打错isTileCLeaned()。然后在您的代码中,None调用它时将得到一个。

  • 您会得到想要的重写功能吗?当然不。
  • None有效的输出?谁知道。
  • 那是预期的行为吗?几乎可以肯定不是。
  • 你会得到一个错误吗?这取决于。

raise NotImplmentedError 强制您实施它,因为在您尝试运行它之前,它将抛出异常。这消除了许多无提示的错误。这类似于为什么几乎没有一个绝不可能是一个好主意的原因:因为人们会犯错误,并且这确保了他们不会被扫除。

注意:使用抽象基类(如其他答案所述)会更好,因为错误将被预先加载,并且程序将在您实现它们之前运行(使用NotImplementedError,它只会在实际调用时抛出异常)。


11

一个人也可以在raise NotImplementedError() 内部@abstractmethod装饰一个基类方法的子 方法。


想象一下为一系列测量模块(物理设备)编写控制脚本。每个模块的功能都严格定义,仅实现一个专用功能:一个功能可以是一组继电器,另一个可以是多通道DAC或ADC,另一个可以是电流表等。

许多正在使用的低级命令将在模块之间共享,例如读取其ID号或向其发送命令。让我们看看现在的情况:

基类

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

共享动词

然后,我们意识到许多特定于模块的命令动词以及因此它们的接口逻辑也被共享。考虑到许多目标模块,下面是3个不同的动词,其含义将不言自明。

  • get(channel)

  • 继电器:获取继电器的开/关状态channel

  • DAC:打开输出电压channel

  • ADC:打开输入电压channel

  • enable(channel)

  • 中继:启用中继功能channel

  • DAC:启用对输出通道的使用channel

  • ADC:启用输入通道的使用channel

  • set(channel)

  • 继电器:channel打开/关闭继电器

  • DAC:输出电压设置为开channel

  • ADC:嗯...没有什么逻辑可想到的。


共享动词成为强制动词

我认为上述动词在各个模块之间共享是一个很好的理由,因为我们看到它们的含义对于每个模块都是显而易见的。我将继续Generic像这样编写我的基类:

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

    @abstractmethod
    def enable(self, channel):
        pass

    @abstractmethod
    def set(self, channel):
        pass

子类

现在我们知道子类都必须定义这些方法。让我们看一下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'

您现在可能想知道:

但这对ADC模块不起作用,因为set上面我们已经看到了这一点!

您说对了:不实施set不是一种选择,因为当您尝试实例化ADC对象时,Python会在下面触发错误。

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

因此,您必须执行一些操作,因为我们制作了set一个强制动词(又名“ @abstractmethod”),该动词已由其他两个模块共享,但是同时,您也不得执行set对此特定模块没有意义的任何 操作。

救援的NotImplementedError

通过像这样完成ADC类:

class ADC(Generic): # ...continued

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

您一次要做三件事:

  1. 您在保护用户免于错误地发出不是(也不应该!)对此模块实施的命令(“设置”)。
  2. 您正在明确地告诉他们问题出在哪里(有关为什么这很重要,请参见TemporalWolf的“裸机异常”链接)
  3. 您正在保护强制动词 确实有意义的所有其他模块的实现。即可以确保那些这些动词模块有意义将实施这些方法和他们这样做究竟使用这些动词,而不是其他一些临时的名称。

-1

您可能想使用@property装饰器,

>>> 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

13
我看不到它是如何解决这个问题的,即何时使用它
TemporalWolf
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.