什么时候使用“提高 NotComplementedError”?

是为了提醒你自己和你的团队正确地实现这个类吗? 我没有完全理解这样一个抽象类的用法:

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


def cleanTileAtPosition(self, pos):
raise NotImplementedError


def isTileCleaned(self, m, n):
raise NotImplementedError
277124 次浏览

正如文档中所说的 < sup > [ docs ] ,

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

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

你可能需要使用 @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

试想一下,如果是这样的话:

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


def cleanTileAtPosition(self, pos):
pass


def isTileCleaned(self, m, n):
pass

你继承了一个子类,却忘了告诉它如何使用 isTileCleaned(),或者,更有可能的是,把它输入为 isTileCLeaned()。然后在您的代码中,当您调用它时,您将得到一个 None

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

你实现它,因为它 威尔抛出一个异常时,你尝试运行它,直到你这样做。这样可以消除许多无声的错误。这与 光着身子从来都不是个好主意的原因相似: 因为人们会犯错误,这样可以确保他们不会被扫地出门。

注意: 正如其他答案所提到的,使用一个抽象基类更好,因为这样错误就会被提前加载,程序在实现它们之前不会运行(对于 NotimplementedError,它只会在实际调用时抛出异常)。

作为 乌列说,它指的是应该在子类中实现的抽象类中的方法,但也可以用来指示 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不一定是空的,可以使用 super()D调用它。

NotImplementedError相比,这样做的一个优点是在实例化时获得显式的 Exception,而不是在方法调用时获得。

还可以执行 raise NotImplementedError() 在里面@abstractmethod修饰的基类方法的子方法。


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

使用中的许多低级命令将在模块之间共享,例如用于读取的命令 他们的身份证号码,或者向他们发送命令。让我们看看我们现在有什么:

基类

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个不同的动词,它们的意思是 将是不言而喻的考虑到一些目标模块。

返回文章页面

  • 继电器: 获取 channel上继电器的开关状态
  • 获得 channel上的 输出电压
  • 获得 channel上的 输入电压

返回文章页面

  • 继电器: 启用 channel上的继电器
  • DAC: 启用 channel上的 输出通道
  • ADC: 启用 channel上的 输入通道

返回文章页面

  • 继电器: 设置 channel的继电器开/关
  • 设置 channel上的 输出电压
  • 嗯... 没有 合乎逻辑出现在我的脑海里。

共享动词变成强制动词

我认为上述动词在模块之间共享是有充分理由的 因为我们看到他们每个人的意思都是显而易见的。我会继续写我的 基类 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不是一个选项,因为 Python 会触发下面的错误 当您尝试实例化您的 ADC 对象时。

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

所以你必须实现一些东西,因为我们把 set变成了 强制动词(也就是“这个抽象方法”) , 它由另外两个模块共享,但是同时也不能实现任何内容 set对这个特定的模块没有意义。

救援错误

通过这样完成 ADC 课程:

class ADC(Generic): # ...continued


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

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

  1. 您正在保护用户不会错误地发出命令(‘ set’) 不(也不应该)为此模块实现。
  2. 你正在告诉他们 明确地的问题是什么(见颞狼的链接关于 为什么这一点如此重要,“仅有的例外”)
  3. 您正在保护 < em > 强制使用谓词的所有其他模块的实现 确实有意义。也就是说,你要确保这些动词 有意义的模块将 实现这些方法和 他们会用这些动词而不是一些 其他特别名称。