在python中嵌套的try / except块是一个很好的编程习惯吗?

Are nested try/except blocks in python a good programming practice?

我正在编写自己的容器,它需要通过属性调用来访问内部的字典。容器的典型用途如下:

1
2
3
4
dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo

我知道写这样的东西可能很愚蠢,但这就是我需要提供的功能。我正在考虑以以下方式实施:

1
2
3
4
5
6
7
8
def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        try:
            return self.dict[item]
        except KeyError:
            print"The object doesn't have such attribute"

我不确定嵌套的try/except块是否是一个好的实践,因此另一种方法是使用hasattr()has_key()

1
2
3
4
5
6
7
8
def __getattribute__(self, item):
        if hasattr(self, item):
            return object.__getattribute__(item)
        else:
            if self.dict.has_key(item):
                return self.dict[item]
            else:
                raise AttributeError("some customised error")

或者使用其中一个,然后尝试如下catch块:

1
2
3
4
5
6
7
8
def __getattribute__(self, item):
    if hasattr(self, item):
        return object.__getattribute__(item)
    else:
        try:
            return self.dict[item]
        except KeyError:
            raise AttributeError("some customised error")

哪种选择是最优雅的Python?


你的第一个例子很好。甚至官方的python文档也推荐这种风格,称为eafp。

就我个人而言,我更喜欢在不必要的时候避免筑巢:

1
2
3
4
5
6
7
8
9
def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass  # fallback to dict
    try:
        return self.dict[item]
    except KeyError:
        raise AttributeError("The object doesn't have such attribute") from None

ps.has_key()在python 2中已被长期弃用。用item in self.dict代替。


在Java中,使用流量控制异常(这主要是因为异常迫使JVM收集资源(这里更详细)),这在Python中有2个重要原则:Duck Typing和EAFP。这基本上意味着,我们鼓励您按照您认为的方式使用一个对象,并在事情不这样时进行处理。

总之,唯一的问题是代码缩进太多。如果你喜欢,试着简化一些像LQC建议的嵌套。


对于您的特定示例,实际上不需要嵌套它们。如果try块中的表达式成功,函数将返回,因此只有在第一次尝试失败时,才会运行整个try/except块之后的任何代码。所以你可以这样做:

1
2
3
4
5
6
7
8
9
10
def __getattribute__(self, item):
    try:
        return object.__getattribute__(item)
    except AttributeError:
        pass
    # execution only reaches here when try block raised AttributeError
    try:
        return self.dict[item]
    except KeyError:
        print"The object doesn't have such attribute"

嵌套它们并不坏,但我觉得让它平放会使结构更清晰:您按顺序尝试一系列事情,并返回第一个有效的。

顺便说一下,您可能想考虑一下您是否真的想在这里使用__getattribute__而不是__getattr__。使用__getattr__将简化操作,因为您将知道常规属性查找过程已经失败。


在我看来,这将是处理这件事的最卑鄙的方式,尽管也因为这会让你的问题变得毫无意义。请注意,这个定义是OCx1(7),而不是__getattribute__(),因为这样做意味着它只需要处理保存在内部字典中的"特殊"属性。

1
2
3
4
5
6
def __getattr__(self, name):
   """only called when an attribute lookup in the usual places has failed"""
    try:
        return self.my_dict[name]
    except KeyError:
        raise AttributeError("some customized error message")


小心点——在这种情况下,首先触摸finally,但也跳过了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def a(z):
    try:
        100/z
    except ZeroDivisionError:
        try:
            print('x')
        finally:
            return 42
    finally:
        return 1


In [1]: a(0)
x
Out[1]: 1


在Python中,请求宽恕比请求允许更容易。不要为嵌套的异常处理操心。

(除此之外,has*几乎总是在任何情况下都使用例外。)


根据文档,最好通过元组或类似的方式处理多个异常:

1
2
3
4
5
6
7
8
9
10
11
12
13
import sys

try:
    f = open('myfile.txt')
    s = f.readline()
    i = int(s.strip())
except IOError as e:
    print"I/O error({0}): {1}".format(e.errno, e.strerror)
except ValueError:
    print"Could not convert data to an integer."
except:
    print"Unexpected error:", sys.exc_info()[0]
    raise


我喜欢避免的一件事是在处理旧的异常时引发新的异常。它会使错误消息变得难以阅读。

例如,在我的代码中,我最初编写

1
2
3
4
try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    raise KeyError(key)

我收到了这个信息。

1
>>> During handling of above exception, another exception occurred.

我想要的是:

1
2
3
4
5
try:
    return tuple.__getitem__(self, i)(key)
except IndexError:
    pass
raise KeyError(key)

它不会影响异常的处理方式。在这两个代码块中,都会捕获keyError。这只是一个获得风格点的问题。


如果try except finally嵌套在finally块中,则最终保留"child"的结果。我还没有找到正式的解释,但是下面的代码片段在Python3.6中显示了这种行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
def f2():
    try:
        a = 4
        raise SyntaxError
    except SyntaxError as se:
        print('log SE')
        raise se from None
    finally:
        try:
            raise ValueError
        except ValueError as ve:
            a = 5
            print('log VE')
            raise ve from None
        finally:
            return 6      
        return a

In [1]: f2()
log SE
log VE
Out[2]: 6


我不认为这是一个关于Python或优雅的问题。这是一个尽可能防止例外的问题。异常是指处理代码或您无法控制的事件中可能发生的错误。在这种情况下,当检查项是属性还是字典时,您拥有完全的控制权,因此避免嵌套的异常,并坚持第二次尝试。