何时使用'raise NotImplementedError'?

Ant*_*ujo 54 python oop

是为了记住自己和你的团队正确实施课程吗?我没有完全使用这样的抽象类:

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

    def cleanTileAtPosition(self, pos):
        raise NotImplementedError

    def isTileCleaned(self, m, n):
        raise NotImplementedError
Run Code Online (Sandbox Code Playgroud)

Uri*_*iel 51

正如文档所述[docs],

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

请注意,虽然主要陈述的用例此错误是应该在继承类上实现的抽象方法的指示,但无论如何您都可以使用它,比如指示TODO标记.

  • @Jérôme 为什么不两者都使用呢?用“abstractmethod”装饰并让它引发“NotImplementedError”。这禁止在派生类中实现“method”时使用“super().method()”。 (8认同)
  • 对于抽象方法,我更喜欢使用`abc`(参见[我的回答](/sf/answers/3102155451/)). (3认同)

Jér*_*ôme 24

正如Uriel所说,它适用于抽象类中应该在子类中实现的方法,但也可用于指示TODO.

第一个用例有一个替代方案:Abstract Base Classes.那些有助于创建抽象类.

这是一个Python 3示例:

class C(abc.ABC):
    @abstractmethod
    def my_abstract_method(self, ...):
    ...
Run Code Online (Sandbox Code Playgroud)

实例化时C,你会得到一个错误,因为它my_abstract_method是抽象的.您需要在子类中实现它.

TypeError: Can't instantiate abstract class C with abstract methods my_abstract_method
Run Code Online (Sandbox Code Playgroud)

子类C和实现my_abstract_method.

class D(C):
    def my_abstract_method(self, ...):
    ...
Run Code Online (Sandbox Code Playgroud)

现在你可以实例化了D.

C.my_abstract_method不必是空的.它可以从D使用中调用super().

这种优势的一个优点NotImplementedError是,您可以Exception在实例化时获得显式,而不是在方法调用时获得.

  • 在 Python 2.6+ 中也可用。只需`from abc import ABCMeta, abstractmethod` 并用`__metaclass__ = ABCMeta` 定义你的ABC。文档:https://docs.python.org/2/library/abc.html (2认同)

Tem*_*olf 17

考虑是否相反:

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

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass
Run Code Online (Sandbox Code Playgroud)

并且你继承并且忘记告诉它如何isTileCleaned()或者,或许更可能的是,它是错误的isTileCLeaned().然后在你的代码中,None当你调用它时,你会得到一个.

  • 你会得到你想要的被覆盖的功能吗?当然不.
  • None有效的输出?谁知道.
  • 这是预期的行为吗?几乎肯定不是.
  • 你会收到错误吗?这取决于.

raise NotImplmentedError 强制你实现它,因为当你尝试运行它时抛出异常,直到你这样做.这消除了很多无声错误.它类似于为什么裸露的除了几乎从来都不是一个好主意:因为人们犯错误,这确保他们不会被扫地.

注意:正如其他答案所提到的那样,使用抽象基类更好,因为错误是前载的,程序在实现之前不会运行(使用NotImplementedError,它只会在实际调用时抛出异常).


pfa*_*bri 17

还可以raise NotImplementedError() @abstractmethod-decorated 基类方法的 child 方法 内部执行 a 。


想象一下为一系列测量模块(物理设备)编写控制脚本。每个模块的功能都被严格定义,仅实现一个专用功能:一个可以是继电器阵列,另一个是多通道 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
Run Code Online (Sandbox Code Playgroud)

共享动词

然后我们意识到许多特定于模块的命令动词以及它们的接口逻辑也是共享的。这里有 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
Run Code Online (Sandbox Code Playgroud)

子类

我们现在知道我们的子类都必须定义这些方法。让我们看看 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'
Run Code Online (Sandbox Code Playgroud)

您现在可能想知道:

但这对ADC模块不起作用,set因为我们刚刚在上面看到的那样毫无意义!

你是对的:不实现set不是一个选项,因为当你尝试实例化你的 ADC 对象时,Python 会触发下面的错误。

TypeError: Can't instantiate abstract class 'ADC' with abstract methods 'set'
Run Code Online (Sandbox Code Playgroud)

所以你必须实现一些东西,因为我们做了set一个强制动词(又名“@abstractmethod”),它被其他两个模块共享,但同时,你也不能实现任何set对这个特定模块没有意义的东西 。

NotImplementedError 救援

通过像这样完成 ADC 课程:

class ADC(Generic): # ...continued

    def set(self, channel):
        raise NotImplementedError("Can't use 'set' on an ADC!")
Run Code Online (Sandbox Code Playgroud)

你同时在做三件非常好的事情:

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