可以在python中的__init__中引发异常吗? 我有这段代码:
class VersionManager(object):
def __init__(self, path):
self._path = path
if not os.path.exists(path): os.mkdir(path)
myfunction(path)
第二行可能会导致异常。 在这种情况下,对象将不会正确初始化。 有没有更好的方法来处理__init__中的代码可能抛出异常的情况?
编辑
在os.mkdir之后添加了对函数的调用
添加了检查以查看目录是否存在
不仅对象没有被正确初始化,没有异常处理程序,VersionManager(path)的调用者可以希望捕获异常并且根本没有实例。
os.mkdir是一种特殊情况,因为它在许多用户不是错误的情况下抛出异常 - 当目录已经存在时。因此,您可能希望明确处理该情况,除了您的__init__函数是否应该引发异常的问题。
谢谢你提醒我。但是,我对一个OSError更好奇,这可能是由于权限被拒绝引起的。
如果VersionManager需要存在目录才能工作,并且其__init__函数无法创建目录,那么允许异常传播是完全合理的。从__init__抛出异常的意思是,"我无法使这个对象起作用,所以我不会让你拥有该对象"。或许更正式地说,"我不能为这个对象建立不变量"。虽然再次创建文件或目录是类不变的特例。通常没有什么可以阻止其他进程删除它,所以"dir存在"不是一个适当的不变量。
Python的可能重复:在__init__中引发异常是不好的形式?
在__init__中引发异常是完全正常的。然后,您将使用try/except包装对象启动/创建调用并对异常做出反应。
然而,一个潜在的奇怪结果是__del__仍在运行:
class Demo(object):
def __init__(self, value):
self.value=value
if value==2:
raise ValueError
def __del__(self):
print '__del__', self.value
d=Demo(1) # successfully create an object here
d=22 # new int object labeled 'd'; old 'd' goes out of scope
# '__del__ 1' is printed once a new name is put on old 'd'
# since the object is deleted with no references
现在尝试使用我们正在测试的值2:
Demo(2)
Traceback (most recent call last):
File"Untitled 3.py", line 11, in
Demo(2)
File"Untitled 3.py", line 5, in __init__
raise ValueError
ValueError
__del__ 2 # But note that `__del__` is still run.
创建值为2的对象会引发ValueError异常并显示仍然运行__del__来清理对象。
请记住,如果在__init__期间引发异常,则对象将无法获得名称。 (然而,它将被创建和销毁。由于__del__与__new__配对,它仍会被调用)
即,就像这样不会创建x:
>>> x=1/0
Traceback (most recent call last):
File"", line 1, in
ZeroDivisionError: integer division or modulo by zero
>>> x
Traceback (most recent call last):
File"", line 1, in
NameError: name 'x' is not defined
潜在的偷偷摸摸:
>>> x='Old X'
>>> x=1/0
Traceback (most recent call last):
File"", line 1, in
ZeroDivisionError: division by zero
>>> x
'Old X'
如果你捕获__init__的异常,同样的事情:
try:
o=Demo(2)
except ValueError:
print o # name error -- 'o' never gets bound to the object...
# Worst still -- 'o' is its OLD value!
所以不要试图引用不完整的对象o - 当你到达except时它已经超出了范围。并且名称o要么没有(即,如果您尝试使用它,则为NameError)或其旧值。
所以结束(感谢Steve Jessop的用户定义的异常想法),你可以包装对象的创建并捕获异常。只需弄清楚如何对您正在查看的操作系统错误作出适当的反应。
所以:
class ForbiddenTwoException(Exception):
pass
class Demo(object):
def __init__(self, value):
self.value=value
print 'trying to create with val:', value
if value==2:
raise ForbiddenTwoException
def __del__(self):
print '__del__', self.value
try:
o=Demo(2)
except ForbiddenTwoException:
print 'Doh! Cant create Demo with a"2"! Forbidden!!!'
# with your example - react to being unusable to create a directory...
打印:
trying to create with val: 2
Doh! Cant create Demo with a"2"! Forbidden!!!
__del__ 2
请注意,如果您想在处理错误时避免"可能",则可以删除所有疑问:class ForbiddenTwoException(ValueError): pass。
如果你认为__del__与__new__配对(并且__init__只是一个可能由__new__调用的函数),那么它需要被调用就不足为奇了。
@SteveJessop:谢谢!好主意...编辑...
@chepner:嗯,C ++方式是只有构造函数成功完成才会执行析构函数。因此,如果你按照你说的术语来考虑它,那么根据你认为"配对"应该意味着它是否令人惊讶:-)对于Python中的另一个例子,你可能会说__enter__与__exit__配对,但是如果__enter__抛出,则不会调用__exit__。我认为最好认为__del__与__new__配对,而__init__是在__new__之后调用的东西,而不是它。
我的观点是,仅仅因为__init__引发了异常,并不意味着__init__尚未完成需要清理的事情。 __new__是Python中的构造函数,而不是__init__(从语义上讲;我知道Python文档将__init__称为构造函数,但__new__实际上构造了对象; __init__之后只是设置了一些字段。
你可以打电话,正如jramirez建议的那样:
try:
ver = VersionManager(path)
except:
raise
或者您可以使用上下文管理器:
class VersionManager(object):
def __init__(self):
#not-so-harmful code
self.path = path
def __enter__(self):
try:
self.path = path
os.mkdir(path)
self.myfunction(path)
except Exception as e:
print e
print"The directory making has failed, the function hasn't been executed."
return self
def __exit__(self, exc_type, exc_value, traceback):
print(exc_type, exc_value, traceback)
并运行它:
with VersionManager(my_path) as myVersionManager:
#do things you want with myVersionManager
这样,您也会在with语句中捕获错误。
def __enter__(self, path):肯定不会奏效。
并且__exit__不会在__enter__中引发任何异常。
是的,我认为那是对的-_-。这就是为什么我也在__enter__函数中使用tryexcept块。
什么是except子句除了重新引发异常之外什么也没做什么?
嗯,你是对的,修好了。
我最喜欢的是简单地输出错误到控制台并继续前进:
import sys, os, traceback
class Myclass
def __init__(self, path):
self._path = path
"""Risky Code"""
try:
os.mkdir(path)
except:
traceback.print_exc(file = sys.stdout)
这样,异常将打印出更像是警告而不是真正的异常。
听起来不错。但是如果另一个函数需要创建目录,它将在except情况下失败。
对不起,这不适用于我。我的所有对象都有某种形式的状态标志。因此,如果目录没有创建,如果您稍后尝试调用函数,则会自动生成错误。我想我们有不同的编程风格。我认为主要问题是如何处理__init__()中的异常。
初始化对象时可以使用try / except。
try:
ver = VersionManager(my_path)
except Exception as e:
# raise e or handle error
print e
并且您需要告诉OP ver会发生什么(例如它甚至不存在)
@MartijnPieters你可以回答......
你已经做了:),但感谢弃用的except上的指针。
@SteveP:正确跟进的时间很短