问题:何时使用“ raise NotImplementedError”?
是为了提醒自己和您的团队正确实施课堂吗?我没有完全使用像这样的抽象类:
class RectangularRoom(object):
def __init__(self, width, height):
raise NotImplementedError
def cleanTileAtPosition(self, pos):
raise NotImplementedError
def isTileCleaned(self, m, n):
raise NotImplementedError
回答 0
如文档所述[docs],
在用户定义的基类中,当抽象方法要求派生类重写该方法时,或者在开发类以指示仍需要添加实际实现时,应引发此异常。
请注意,尽管主要说明的用例是该错误是对应该在继承的类上实现的抽象方法的指示,但是您可以随意使用它,例如用于TODO
标记的指示。
回答 1
正如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
考虑一下是否是:
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,它只会在实际调用时抛出异常)。
回答 3
一个人也可以在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!")
您一次要做三件事:
- 您在保护用户免于错误地发出不是(也不应该!)对此模块实施的命令(“设置”)。
- 您正在明确地告诉他们问题出在哪里(有关为什么这很重要,请参见TemporalWolf的“裸机异常”链接)
- 您正在保护强制动词 确实有意义的所有其他模块的实现。即可以确保那些这些动词模块做有意义将实施这些方法和他们这样做究竟使用这些动词,而不是其他一些临时的名称。
回答 4
您可能想使用@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