【问题标题】:Handle exception in __init__处理 __init__ 中的异常
【发布时间】:2013-11-18 22:57:43
【问题描述】:

可以在 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) 的调用者充其量只能希望捕获异常并拥有 no 实例。
  • os.mkdir 有点特殊,因为它会在对许多用户来说不是错误的情况下抛出异常——当目录已经存在时。所以你可能想明确地处理这种情况,除了你的__init__函数是否应该引发一般异常的问题。
  • 感谢您提醒我。但是,我对 OSError 更好奇,这可能是由权限被拒绝引起的。
  • 如果VersionManager需要目录存在才能工作,而其__init__函数无法创建目录,那么允许异常传播是完全合理的。从__init__ 抛出异常的意思是,“我不能让这个对象工作,所以我不会让你拥有这个对象”。或者更正式地说,“我不能为这个对象建立不变量”。尽管再次创建文件或目录是类不变量的特殊情况。通常没有什么可以阻止其他进程删除它,因此“目录存在”不是适当的不变量。

标签: python exception


【解决方案1】:

__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 <module>
    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 "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

潜行者:

>>> x='Old X'
>>> x=1/0
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
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 提出的User Defined Exception 想法),您可以包装对象的创建并捕获异常。只需弄清楚如何对您正在查看的操作系统错误做出适当的反应。

所以:

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__ 调用的函数),那么它需要被调用也就不足为奇了.
  • @chepner:嗯,C++ 的方式是只有在构造函数成功完成时才会执行析构函数。因此,如果您按照您所说的术语来考虑它,那么根据您认为“配对”应该意味着什么,这是否令人惊讶:-) 对于 Python 中的另一个示例,您可能会说 __enter____exit__ 配对,但是如果__enter__ 抛出,则不会调用__exit__。我认为最好将__del____new__ 配对,而__init__ 被称为after __new__,而不是by 它。
  • 我的意思是,仅仅因为__init__ 引发了异常,并不意味着__init__ 还没有完成需要清理的事情。 __new__ Python中的构造函数,而不是__init__(从语义上讲;我知道Python文档将__init__称为构造函数,但__new__实际上构造了对象; __init__ 只是在之后设置了一些字段。
【解决方案2】:

您可以按照 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 语句中发现错误。

【讨论】:

  • 并且__exit__ 不会在__enter__ 中引发任何异常。
  • 是的,我认为这是正确的-_-。这就是为什么我还在__enter__ 函数中使用try/except 块。
  • 除了重新引发异常之外什么都不做的except 子句有什么意义?
【解决方案3】:

初始化对象时可以使用try/except。

try:

    ver = VersionManager(my_path)

except Exception as e:
    # raise e or handle error
    print e

【讨论】:

  • 你需要告诉 OP ver 发生了什么(例如它甚至不存在)
  • 您已经这样做了 :),但感谢已弃用的 except 上的指针。
  • @SteveP.:正确跟进的时间不多
【解决方案4】:

我最喜欢的是简单地将错误输出到控制台并继续前进:

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__() 中的异常。
猜你喜欢
  • 2018-08-19
  • 2020-06-26
  • 1970-01-01
  • 1970-01-01
  • 2021-09-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多