【问题标题】:Are nested try/except blocks in Python a good programming practice?Python 中的嵌套 try/except 块是一种好的编程习惯吗?
【发布时间】:2013-06-05 14:21:21
【问题描述】:

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

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

哪个选项最 Pythonic 和优雅?

【问题讨论】:

  • 非常感谢提供if 'foo' in dict_container:的蟒蛇大神。阿门。

标签: python


【解决方案1】:

在我看来,这将是处理它的最 Pythonic 的方式,尽管并且因为它使您的问题没有实际意义。请注意,这定义了__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")

【讨论】:

  • 请注意,在 except 块中引发异常可能会在 Python 3 中产生令人困惑的输出。这是因为(根据 PEP 3134)Python 3 将第一个异常(KeyError)跟踪为“上下文"的第二个异常(AttributeError),如果它到达顶层,它将打印一个包含两个异常的回溯。当不期望出现第二个异常时,这可能会有所帮助,但是如果您故意引发第二个异常,则这是不可取的。对于 Python 3.3,PEP 415 添加了使用 raise AttributeError("whatever") from None 抑制上下文的功能。
  • @Blckknght:在这种情况下,打印包含两个异常的回溯就可以了。换句话说,我不认为你笼统地说它总是不受欢迎的说法是正确的。在此处的用法中,它将KeyError 转换为AttributeError 并表明在回溯中发生的事情将是有用且适当的。
  • 对于更复杂的情况你可能是对的,但我认为当你在异常类型之间转换时,你通常知道第一个异常的细节对外部用户并不重要。也就是说,如果__getattr__ 引发异常,则该错误可能是属性访问中的拼写错误,而不是当前类代码中的实现错误。将早期的异常显示为上下文可能会混淆这一点。即使您使用raise Whatever from None 抑制上下文,如果需要,您仍然可以通过ex.__context__ 获得先前的异常。
  • 我想接受你的回答,但是在这个问题中,我更好奇使用嵌套的 try/catch 块是否是一个好习惯。另一方面,它是最优雅的解决方案,我将在我的代码中使用它。非常感谢马丁。
  • 迈克尔:不客气。它也比使用__getattribute__() 更快。
【解决方案2】:

嵌套 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,所以我们不需要创建这个函数。)

【讨论】:

    【解决方案3】:

    如果 try-except-finally 嵌套在 finally 块中,“child” finally 的结果将被保留。我还没有找到官方的解释,但是下面的代码 sn-p 显示了 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
    

    【讨论】:

    • 当 finally 嵌套在 except 块中时,此行为与 @Sławomir Lenart 给出的示例不同。
    【解决方案4】:

    请小心 - 在这种情况下,第一个 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
    

    【讨论】:

    • 哇,这让我大吃一惊……你能给我指出一些解释这种行为的文档片段吗?
    • @Michal: 仅供参考:finally 的两个块都为 a(0) 执行,但只返回父 finally-return
    【解决方案5】:

    根据the documentation,最好通过元组或者这样处理多个异常:

    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
    

    【讨论】:

    • 这个答案并没有真正解决原始问题,但对于任何阅读它的人来说,除了最后之外,“裸露”是一个糟糕的主意(通常),因为它会捕获所有内容,包括 NameError 和 KeyboardInterrupt -这通常不是你的意思!
    • 鉴于代码在 print 语句之后立即重新引发相同的异常,这真的很重要吗?在这种情况下,它可以在不隐藏异常的情况下提供更多有关异常的上下文。如果它没有重新加注,那么我完全同意,但我认为隐藏你不打算隐藏的异常没有风险。
    【解决方案6】:

    我喜欢在处理旧异常时避免引发新异常。它使错误消息难以阅读。

    例如,在我的代码中,我最初是这样写的

    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。这只是获得风格点的问题。

    【讨论】:

    • 查看接受的答案对 raise from None 的使用,以获得 更多 风格点。 :)
    【解决方案7】:

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

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

    【讨论】:

    • 来自文档:在多线程环境中,LBYL(在你跳跃之前查看)方法可能会在“查看”和“跳跃”。例如,如果另一个线程在测试之后但在查找之前从映射中删除键,则代码 if key in mapping: return mapping[key] 可能会失败。此问题可以通过锁定或使用 EAFP(请求宽恕比许可更容易)方法来解决。
    【解决方案8】:

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

    总而言之,唯一的问题是您的代码缩进过多。如果您愿意,请尝试简化一些嵌套,例如 the suggested answer above 中建议的 lqc

    【讨论】:

      【解决方案9】:

      在 Python 中是easier to ask for forgiveness than permission。不要担心嵌套异常处理。

      (此外,has* 几乎总是在背后使用异常。)

      【讨论】:

        【解决方案10】:

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

        【讨论】:

        • return object.__getattribute__(item) 不正确,将生成 TypeError,因为传递的参数数量错误。它应该是return object.__getattribute__(self, item)
        • PEP 20:平面优于嵌套。
        • 最后一行的from None 是什么意思?
        • @niklas 它本质上抑制了异常上下文(“在处理此异常期间发生了另一个异常”-esque 消息)。见here
        • Python 文档推荐嵌套 try 的事实有点疯狂。这显然是一种可怕的风格。处理可能会失败的一系列操作的正确方法是使用某种 Python 不支持的单子构造。
        【解决方案11】:

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

        【讨论】:

          猜你喜欢
          • 2013-04-14
          • 1970-01-01
          • 2017-12-26
          • 2012-10-06
          • 2015-04-14
          • 2020-09-23
          • 2017-12-09
          • 2015-08-03
          相关资源
          最近更新 更多