Python中嵌套的try/except块是一个好的编程实践吗?

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

dict_container = DictContainer()
dict_container['foo'] = bar
...
print dict_container.foo

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

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():

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")

或者像这样使用其中一个和一个try catch块:

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化和最优雅的?

290284 次浏览

在Python中,它是请求原谅比请求允许容易。不要担心嵌套异常处理。

(此外,has*几乎总是在封面下使用异常。)

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

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__将简化事情,因为您将知道正常的属性查找过程已经失败。

虽然在Java中使用异常进行流控制确实是一种糟糕的做法(主要是因为异常迫使JVM收集资源(更多的在这里)),但在Python中有两个重要的原则:duck typingEAFP。这基本上意味着鼓励您尝试以您认为它可以工作的方式使用对象,并在事情不是这样的情况下处理。

总之,唯一的问题是您的代码有太多的缩进。如果你喜欢,试着简化一些嵌套,就像上面的建议答案中建议的lqc

你的第一个例子完全没问题。甚至官方的Python文档也推荐这种名为EAFP的样式。

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

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

我不认为这是python或优雅的问题。这是一个尽可能防止异常的问题。异常用于处理代码中可能发生的错误或无法控制的事件。

在这种情况下,您可以完全控制检查项是否是属性或在字典中,因此避免嵌套异常并坚持第二次尝试。

在我看来,这将是最python化的处理方式,尽管而且因为它使您的问题变得毫无意义。注意,这里定义了__getattr__()而不是__getattribute__() ,因为这样做意味着它只需要处理"special"属性保存在内部字典中。

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")

我喜欢避免在处理旧异常时引发新异常。它使错误消息读起来很混乱。

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

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

我得到了这个信息。

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

我想要的是:

try:
return tuple.__getitem__(self, i)(key)
except IndexError:
pass
raise KeyError(key)

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

根据的文档,最好通过元组或像这样处理多个异常:

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

只是要小心-在这种情况下,第一个finally被触摸,也被跳过。

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

如果try-except-finally嵌套在最后块中,则"child"最后被保存下来。我还没有找到官方的解释,但是下面的代码片段显示了Python 3.6中的这种行为。

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

一个简单的嵌套try/except的例子如下:

import numpy as np


def divide(x, y):
try:
out = x/y
except:
try:
out = np.inf * x / abs(x)
except:
out = np.nan
finally:
return out


现在尝试不同的组合,你会得到正确的结果:

divide(15, 3)
# 5.0


divide(15, 0)
# inf


divide(-15, 0)
# -inf


divide(0, 0)
# nan


(当然,我们有NumPy,所以我们不需要创建这个函数。)