259

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

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

或者使用其中一个和一个 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 和优雅?

4

11 回答 11

230

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

于 2013-06-09T23:46:45.417 回答
23

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

总之,唯一的问题是您的代码缩进过多。如果您愿意,请尝试简化一些嵌套,例如上面建议的答案中建议的lqc

于 2013-06-09T23:46:11.913 回答
16

请小心 - 在这种情况下,第一个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
于 2018-03-26T17:59:59.677 回答
13

对于您的具体示例,您实际上不需要嵌套它们。如果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-06-09T23:45:59.897 回答
8

在我看来,这将是处理它的最 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")
于 2013-06-09T23:52:16.217 回答
8

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

于 2019-09-04T14:13:52.937 回答
5

在 Python 中,请求原谅比请求许可更容易。不要为嵌套的异常处理出汗。

(此外,has*无论如何,几乎总是使用异常。)

于 2013-06-09T23:40:37.497 回答
5

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

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
于 2016-01-14T18:15:08.580 回答
3

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

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

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

于 2015-02-02T15:04:57.607 回答
2

如果try-except-finally嵌套在finally块中,则“child” finally 的结果将被保留。我还没有找到官方的解释,但是下面的代码片段显示了 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
于 2019-02-25T20:48:42.463 回答
0

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

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

于 2013-06-09T23:49:30.577 回答