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


200

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

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

或者使用其中之一,然后尝试使用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")

哪个选项最适合pythonic和优雅?


将感谢python神提供的if 'foo' in dict_container:。阿们
gseattle

Answers:


180

您的第一个例子很好。甚至官方的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改用。


2
return object.__getattribute__(item)是不正确的,并且会TypeError因为传递的参数数量错误而产生一个。相反,它应该是return object.__getattribute__(self, item)
martineau 2014年

13
PEP 20:扁平比嵌套好。
Ioannis Filippidis

7
from None最后一行是什么意思?
尼克拉斯

2
@niklas它本质上抑制了异常上下文(“在处理此异常期间发生了另一个异常”类消息)。看到这里
Kade

Python文档推荐嵌套尝试的事实有点发疯。这显然是可怕的风格。处理可能失败的操作链的正确方法是使用某种Python不支持的monadic构造。
亨利·亨林森

19

尽管在Java中使用Exceptions进行流控制确实是一个不好的做法(主要是因为异常迫使jvm收集资源(更多信息请参见此处)),但在Python中,您有2条重要的原则:Duck TypingEAFP。基本上,这意味着鼓励您尝试以您认为可行的方式使用对象,并在情况并非如此时进行处理。

总之,唯一的问题是您的代码缩进过多。如果您喜欢,请尝试简化一些嵌套,例如lqc建议


10

请注意-在这种情况下,首先会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

哇,真让我大吃一惊...您能指出一些解释此行为的文档片段吗?
米歇尔

1
@Michal:fyi:两个代码finally块均针对执行a(0),但仅finally-return返回父对象。
斯瓦沃米尔Lenart

9

对于您的特定示例,您实际上不需要嵌套它们。如果表达式在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__将简化事情,因为您将知道常规属性查找过程已经失败。


7

我认为这将是处理该问题的最Python方式,尽管因为它使您的问题无济于事。请注意,这__getattr__()不是定义而是__getattribute__()因为这样做意味着它只需要处理保留在内部字典中的“特殊”属性。

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

2
请注意,在except块中引发异常可能会使Python 3产生混乱的输出。这是因为(按照PEP 3134)Python 3会将第一个异常(the KeyError)作为第二个异常(the AttributeError)的“上下文”来跟踪,如果到达了顶层,它将打印出包含两个异常的回溯。当不期望发生第二个异常时,这可能会有所帮助,但是如果您故意引发第二个异常,则这是不可取的。对于Python 3.3,PEP 415添加了使用抑制上下文的功能raise AttributeError("whatever") from None
Blckknght

3
@Blckknght:在这种情况下,打印包含两个异常的回溯是可以的。换句话说,我不认为您的一再声明总是不受欢迎的说法是正确的。在这里的用法中,它变成了KeyErroran,AttributeError并表明在回溯中发生的事情将是有用且适当的。
martineau 2013年

对于更复杂的情况,您可能是正确的,但是我认为在异常类型之间进行转换时,您通常会知道第一个异常的详细信息与外部用户无关。也就是说,如果__getattr__引发异常,则该错误可能是属性访问中的错字,而不是当前类代码中的实现错误。将较早的异常显示为上下文可能会造成混乱。即使使用抑制上下文raise Whatever from None,如果需要,您仍然可以通过来获得先前的异常ex.__context__
Blckknght

1
我想接受您的回答,但是在这个问题中,我更好奇是否使用嵌套的try / catch块是一种好习惯。另一方面,这是最优雅的解决方案,我将在代码中使用它。非常感谢马丁。
Michal 2013年

米哈尔:不客气。它也比使用更快__getattribute__()
martineau 2013年


4

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

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

2
这个答案并没有真正解决最初的问题,但是对于任何阅读它的人来说,注意到“裸露”是一个可怕的想法(通常),因为最后它会捕获所有内容,例如NameError和KeyboardInterrupt-这通常不是您的意思!
丢失

鉴于代码在print语句之后立即重新引发了相同的异常,这真的很重要。在这种情况下,它可以提供有关异常的更多上下文而不隐藏它。如果没有重新提出,我将完全同意,但我认为隐藏您不希望的异常没有风险。
NimbusScale

4

嵌套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,所以我们不需要创建此函数]


2

我要避免的一件事是在处理旧异常时引发新异常。它使错误消息难以理解。

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

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。这仅仅是获取样式点的问题。


from None不过,有关更多风格方面的信息,请参见接受的答案对加注的使用。:)
Pianosaurus

1

如果将try-except-finally嵌套在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

此行为与@SławomirLenart给出的示例不同,后者最终嵌套在除了block内。
舒光华

0

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


来自文档:在多线程环境中,LBYL(跨越式)技术可能会在“外观”与“跳跃”之间引入竞争条件。例如,如果另一个线程在测试之后但在查找之前从映射中删除了键,则代码if if in mapping:return mapping [key]可能会失败。可以使用锁或使用EAFP(更容易请求宽恕而不是允许)方法
努诺·安德烈
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.