何时使用“ raise NotImplementedError”?

问题:何时使用“ 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

Is it to remind yourself and your team to implement the class correctly? I don’t fully get the use of an abstract class like this:

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标记的指示。

As the documentation states [docs],

In user defined base classes, abstract methods should raise this exception when they require derived classes to override the method, or while the class is being developed to indicate that the real implementation still needs to be added.

Note that although the main stated use case this error is the indication of abstract methods that should be implemented on inherited classes, you can use it anyhow you’d like, like for indication of a TODO marker.


回答 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在实例化时(而不是在方法调用时)获得显式信息。

As Uriel says, it is meant for a method in an abstract class that should be implemented in child class, but can be used to indicate a TODO as well.

There is an alternative for the first use case: Abstract Base Classes. Those help creating abstract classes.

Here’s a Python 3 example:

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

When instantiating C, you’ll get an error because my_abstract_method is abstract. You need to implement it in a child class.

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

Subclass C and implement my_abstract_method.

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

Now you can instantiate D.

C.my_abstract_method does not have to be empty. It can be called from D using super().

An advantage of this over NotImplementedError is that you get an explicit Exception at instantiation time, not at method call time.


回答 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,它只会在实际调用时抛出异常)。

Consider if instead it was:

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

    def cleanTileAtPosition(self, pos):
        pass

    def isTileCleaned(self, m, n):
        pass

and you subclass and forget to tell it how to isTileCleaned() or, perhaps more likely, typo it as isTileCLeaned(). Then in your code, you’ll get a None when you call it.

  • Will you get the overridden function you wanted? Definitely not.
  • Is None valid output? Who knows.
  • Is that intended behavior? Almost certainly not.
  • Will you get an error? It depends.

raise NotImplmentedError forces you to implement it, as it will throw an exception when you try to run it until you do so. This removes a lot of silent errors. It’s similar to why a bare except is almost never a good idea: because people make mistakes and this makes sure they aren’t swept under the rug.

Note: Using an abstract base class, as other answers have mentioned, is better still, as then the errors are frontloaded and the program won’t run until you implement them (with NotImplementedError, it will only throw an exception if actually called).


回答 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!")

您一次要做三件事:

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

One could also do a raise NotImplementedError() inside the child method of an @abstractmethod-decorated base class method.


Imagine writing a control script for a family of measurement modules (physical devices). The functionality of each module is narrowly-defined, implementing just one dedicated function: one could be an array of relays, another a multi-channel DAC or ADC, another an ammeter etc.

Much of the low-level commands in use would be shared between the modules for example to read their ID numbers or to send a command to them. Let’s see what we have at this point:

Base Class

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

Shared Verbs

We then realise that much of the module-specific command verbs and, therefore, the logic of their interfaces is also shared. Here are 3 different verbs whose meaning would be self-explanatory considering a number of target modules.

  • get(channel)

  • relay: get the on/off status of the relay on channel

  • DAC: get the output voltage on channel

  • ADC: get the input voltage on channel

  • enable(channel)

  • relay: enable the use of the relay on channel

  • DAC: enable the use of the output channel on channel

  • ADC: enable the use of the input channel on channel

  • set(channel)

  • relay: set the relay on channel on/off

  • DAC: set the output voltage on channel

  • ADC: hmm… nothing logical comes to mind.


Shared Verbs Become Enforced Verbs

I’d argue that there is a strong case for the above verbs to be shared across the modules as we saw that their meaning is evident for each one of them. I’d continue writing my base class Generic like so:

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

    @abstractmethod
    def enable(self, channel):
        pass

    @abstractmethod
    def set(self, channel):
        pass

Subclasses

We now know that our subclasses will all have to define these methods. Let’s see what it could look like for the ADC module:

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'

You may now be wondering:

But this won’t work for the ADC module as set makes no sense there as we’ve just seen this above!

You’re right: not implementing set is not an option as Python would then fire the error below when you tried to instantiate your ADC object.

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

So you must implement something, because we made set an enforced verb (aka ‘@abstractmethod’), which is shared by two other modules but, at the same time, you must also not implement anything as set does not make sense for this particular module.

NotImplementedError to the Rescue

By completing the ADC class like this:

class ADC(Generic): # ...continued

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

You are doing three very good things at once:

  1. You are protecting a user from erroneously issuing a command (‘set’) that is not (and shouldn’t!) be implemented for this module.
  2. You are telling them explicitly what the problem is (see TemporalWolf’s link about ‘Bare exceptions’ for why this is important)
  3. You are protecting the implementation of all the other modules for which the enforced verbs do make sense. I.e. you ensure that those modules for which these verbs do make sense will implement these methods and that they will do so using exactly these verbs and not some other ad-hoc names.

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

You might want to you use the @property decorator,

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