Python 何时使用“引发 NotImplementedError”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44315961/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 23:54:51  来源:igfitidea点击:

When to use 'raise NotImplementedError'?

pythonoop

提问by Antonio Araujo

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

回答by Uriel

As the documentation states [docs],

正如文档所述[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 TODOmarker.

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

回答by Jér?me

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.

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

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

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

Here's a Python 3 example:

这是一个 Python 3 示例:

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

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

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

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

Subclass Cand implement my_abstract_method.

子类C并实现my_abstract_method

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

Now you can instantiate D.

现在您可以实例化D.

C.my_abstract_methoddoes not have to be empty. It can be called from Dusing super().

C.my_abstract_method不必为空。可以通过D使用调用它super()

An advantage of this over NotImplementedErroris that you get an explicit Exceptionat instantiation time, not at method call time.

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

回答by TemporalWolf

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 Nonewhen you call it.

并且您子类化并忘记告诉它如何isTileCleaned(),或者更有可能将其输入为isTileCLeaned(). 然后在你的代码中,None当你调用它时你会得到一个。

  • Will you get the overridden function you wanted? Definitely not.
  • Is Nonevalid output? Who knows.
  • Is that intended behavior? Almost certainly not.
  • Will you get an error? It depends.
  • 你会得到你想要的覆盖函数吗?当然不。
  • 是否None有效输出?谁知道。
  • 这是预期的行为吗?几乎可以肯定不是。
  • 你会得到错误吗?这取决于。

raise NotImplmentedErrorforcesyou to implement it, as it willthrow 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.

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

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

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

回答by pfabri

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

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



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.

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

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:

许多正在使用的低级命令将在模块之间共享,例如读取它们的 ID 号或向它们发送命令。让我们看看此时我们有什么:

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.

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

  • get(channel)

    • relay:get the on/off status of the relay on channel
    • DAC:get the outputvoltage on channel
    • ADC:get the inputvoltage on channel
  • enable(channel)

    • relay:enable the use of the relay on channel
    • DAC:enable the use of the outputchannel on channel
    • ADC:enable the use of the inputchannel on channel
  • set(channel)

    • relay:set the relay on channelon/off
    • DAC:set the outputvoltage on channel
    • ADC:hmm... nothing logicalcomes to mind.
  • get(channel)

    • 继电器:获取继电器的开/关状态channel
    • DAC:打开输出电压channel
    • ADC:获取输入电压channel
  • enable(channel)

    • 继电器:启用继电器的使用channel
    • DAC:启用输出通道channel
    • ADC:启用输入通道的使用channel
  • set(channel)

    • 继电器:设置继电器channel开/关
    • DAC:设置输出电压channel
    • ADC:嗯……脑子里没有任何合乎逻辑的东西。


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 Genericlike so:

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

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:

我们现在知道我们的子类都必须定义这些方法。让我们看看 ADC 模块的样子:

class ADC(Generic):

    def __init__(self):
        super().__init__()  #< applies to all modules
        # more innit 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 ADCmodule as setmakes no sense there as we've just seen this above!

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

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

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

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

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

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

NotImplementedError to the Rescue

NotImplementedError 救援

By completing the ADC class like this:

通过像这样完成 ADC 课程:

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 explicitlywhat 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 verbsdo make sense. I.e. you ensure that those modules for which these verbs domake sense will implement these methods and that they will do so using exactly these verbsand not some other ad-hoc names.
  1. 您正在保护用户不会错误地发出不(也不应该!)为此模块实现的命令(“set”)。
  2. 您正在明确地告诉他们问题是什么(请参阅 TemporalWolf 关于“裸异常”的链接,了解为什么这很重要)
  3. 您正在保护强制动词对其有意义的所有其他模块的实现。即可以确保那些这些动词模块有意义将实施这些方法和他们这样做究竟使用这些动词,而不是其他一些临时的名称。

回答by Simeon Aleksov

You might want to you use the @propertydecorator,

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