在
__init__
内提出异常是否被认为是不好的形式?如果是这样,那么当某些类变量初始化为 None
或类型不正确时,可接受的抛出错误的方法是什么?
在
__init__()
内引发异常绝对没问题。没有其他好的方法来指示初始化程序中的错误情况,并且标准库中有数百个示例,其中初始化对象可能会引发异常。
当然,要提出的错误类别取决于您。如果初始化程序传递了无效参数,则
ValueError
是最好的。
确实,在构造函数中指示错误的唯一正确方法是引发异常。不过,您需要注意异常安全。在C++等面向对象语言中,如果对象的构造函数抛出异常(意味着对象的初始化不完整),则不会调用析构函数。在Python中,析构函数总是被调用。例如,如果
AttributeError
失败,以下代码会在 self.stream.close()
处抛出 self.socket.connect()
:
class Connection:
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 中的资源。下面的替代方案使用上下文管理器。它不需要在构造函数中引发异常,并且如果
__enter__()
引发异常,则不会调用 __exit__()
。
import socket
class Connection:
def __init__(self, address):
self.address = address
def __enter__(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(self.address)
self.stream = self.socket.makefile()
return self.stream
def __exit__(self, exc_type, exc_value, exc_traceback):
self.stream.close()
self.socket.close()
return False
with Connection(("localhost", 22)) as c:
print(c.readline())
我看不出有任何理由认为它是不好的形式。
相反,与返回错误代码相反,众所周知,异常的优点之一是错误代码通常不能由构造函数返回。因此,至少在 C++ 这样的语言中,引发异常是发出错误信号的唯一方法。
标准库说:
>>> 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'
而且我真的不明白为什么它应该被认为是不好的形式。
我应该认为这是内置
ValueError
异常的完美案例。
在某些情况下,从 init 引发错误是不可避免的,但在 init 中做太多工作是一种不好的风格。您应该考虑创建一个工厂或伪工厂 - 一个返回已设置对象的简单类方法。
我同意以上所有内容。
除了引发异常之外,确实没有其他方法可以表明对象初始化过程中出现了问题。
在大多数程序类中,类的状态完全依赖于该类的输入,我们可能期望引发某种 ValueError 或 TypeError。
如果(例如)网络设备不可用或画布对象无法写入,具有副作用的类(例如,执行网络或图形操作的类)可能会在 init 中引发错误。这对我来说听起来很明智,因为您通常想尽快了解故障情况。
不要在上下文管理器调用的构造函数中引发异常!
示例: 如果
contextlib.closing
这样使用:
with closing(Foobar()) as foobar:
foobar.my_method()
那么如果在对象构造函数中引发异常,
foobar.close()
将NOT被调用!