Python:在__init__中引发异常是否不好?

问题:Python:在__init__中引发异常是否不好?

在其中引发异常是否被认为是不好的形式__init__?如果是这样,那么当某些类变量初始化为None错误类型或类型错误时,可以接受的引发错误的方法是什么?

Is it considered bad form to raise exceptions within __init__? If so, then what is the accepted method of throwing an error when certain class variables are initialized as None or of an incorrect type?


回答 0

在内部引发异常__init__()是绝对可以的。在构造函数中没有其他好的方法来指示错误情况,并且标准库中有数百个示例,在这些示例中构建对象会引发异常。

当然,要提出的错误类别由您决定。ValueError如果向构造函数传递了无效的参数,则最好。

Raising exceptions within __init__() is absolutely fine. There’s no other good way to indicate an error condition within a constructor, and there are many hundreds of examples in the standard library where building an object can raise an exception.

The error class to raise, of course, is up to you. ValueError is best if the constructor was passed an invalid parameter.


回答 1

确实,在构造函数中指示错误的唯一正确方法是引发异常。这就是为什么在C ++和其他考虑到异常安全性设计的面向对象的语言中,如果在对象的构造函数中抛出异常(表示对象的初始化不完整),则不会调用析构函数。在脚本语言(例如Python)中通常不是这种情况。例如,如果socket.connect()失败,以下代码将引发AttributeError:

class NetworkInterface:
    def __init__(self, address)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(address)
        self.stream = self.socket.makefile()

    def __del__(self)
        self.stream.close()
        self.socket.close()

原因是在连接尝试失败之后,流属性初始化之前,调用了不完整对象的析构函数。您不应该避免从构造函数中引发异常,我只是说很难在Python中编写完全安全的异常代码。一些Python开发人员完全避免使用析构函数,但这是另一个参数的问题。

It’s true that the only proper way to indicate an error in a constructor is raising an exception. That is why in C++ and in other object-oriented languages that have been designed with exception safety in mind, the destructor is not called if an exception is thrown in the constructor of an object (meaning that the initialization of the object is incomplete). This is often not the case in scripting languages, such as Python. For example, the following code throws an AttributeError if socket.connect() fails:

class NetworkInterface:
    def __init__(self, address)
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.socket.connect(address)
        self.stream = self.socket.makefile()

    def __del__(self)
        self.stream.close()
        self.socket.close()

The reason is that the destructor of the incomplete object is called after the connection attempt has failed, before the stream attribute has been initialized. You shouldn’t avoid throwing exceptions from constructors, I’m just saying that it’s difficult to write fully exception safe code in Python. Some Python developers avoid using destructors altogether, but that’s a matter of another debate.


回答 2

我看不出任何形式的错误。

相反,与返回错误代码相反,已知异常处理得很好的原因之一是,构造函数通常无法返回错误代码。因此,至少在像C ++这样的语言中,引发异常是发出错误的唯一途径。

I don’t see any reason that it should be bad form.

On the contrary, one of the things exceptions are known for doing well, as opposed to returning error codes, is that error codes usually can’t be returned by constructors. So at least in languages like C++, raising exceptions is the only way to signal errors.


回答 3

标准库说:

>>> f = file("notexisting.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'notexisting.txt'

我也没有真正看到任何理由将其视为错误的形式。

The standard library says:

>>> f = file("notexisting.txt")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IOError: [Errno 2] No such file or directory: 'notexisting.txt'

Also I don’t really see any reason why it should be considered bad form.


回答 4

我应该认为这是内置ValueError异常的完美案例。

I should think it is the perfect case for the built-in ValueError exception.


回答 5

我同意以上所有观点。

除了引发异常外,实际上没有其他方法可以表明对象的初始化出错。

在大多数程序类中,类的状态完全取决于该类的输入,我们可能期望引发某种ValueError或TypeError。

如果(例如)网络设备不可用或无法写入画布对象,则具有副作用的类(例如,进行网络或图形处理的类)可能会在初始化中引发错误。这对我来说听起来很合理,因为您通常希望尽快了解故障情况。

I concur with all of the above.

There’s really no other way to signal that something went wrong in the initialisation of an object other than raising an exception.

In most programs classes where the state of a class is wholly dependant on the inputs to that class we might expect some kind of ValueError or TypeError to be raised.

Classes with side-effects (e.g. one which does networking or graphics) might raise an error in init if (for example) the network device is unavailable or the canvas object cannot be written to. This sounds sensible to me because often you want to know about failure conditions as soon as possible.


回答 6

在某些情况下,不可避免地要从init引发错误,但是init太多的工作是不好的风格。您应该考虑建立工厂或伪工厂-一种简单的类方法,该方法返回设置的对象。

Raising errors from init is unavoidable in some cases, but doing too much work in init is a bad style. You should consider making a factory or a pseudo-factory – a simple classmethod that returns setted up object.