问题: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()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和优雅?

I’m writing my own container, which needs to give access to a dictionary inside by attribute calls. The typical use of the container would be like this:

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

I know that it might be stupid to write something like this, but that’s the functionality I need to provide. I was thinking about implementing this in a following way:

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"

I’m not sure whether nested try/except blocks are a good practice so another way would be to use 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")

Or to use one of them and one try catch block like this:

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

Which option is most pythonic and elegant?


回答 0

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

Your first example is perfectly fine. Even the official Python docs recommend this style known as EAFP.

Personally, I prefer to avoid nesting when it’s not necessary:

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() has been deprecated for a long time in Python 2. Use item in self.dict instead.


回答 1

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

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

While in Java its indeed a bad practice to use Exceptions for flow control (mainly because exceptions force the jvm to gather resources (more here)), in Python you have 2 important principles: Duck Typing and EAFP. This basically means that you are encouraged to try using an object the way you think it would work, and handle when things are not like that.

In summary the only problem would be your code getting too much indented. If you feel like it, try to simplify some of the nestings like lqc suggested in the suggested answer above.


回答 2

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

Just be careful – in this case first finally is touched BUT skipped too.

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

回答 3

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

For your specific example, you don’t actually need to nest them. If the expression in the try block succeeds, the function will return, so any code after the whole try/except block will only be run if the first attempt fails. So you can just do:

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"

Nesting them isn’t bad, but I feel like leaving it flat makes the structure more clear: you’re sequentially trying a series of things and returning the first one that works.

Incidentally, you might want to think about whether you really want to use __getattribute__ instead of __getattr__ here. Using __getattr__ will simplify things because you’ll know that the normal attribute lookup process has already failed.


回答 4

我认为这将是处理该问题的最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")

In my opinion this would be the most Pythonic way to handle it, although and because it makes your question moot. Note that this defines__getattr__()instead of__getattribute__() because doing so means it only has to deal with the “special” attributes being keep in the internal dictionary.

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

回答 5

在Python中,请求宽恕比允许容易。不要汗水嵌套异常处理。

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

In Python it is easier to ask for forgiveness than permission. Don’t sweat the nested exception handling.

(Besides, has* almost always uses exceptions under the cover anyways.)


回答 6

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

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

According to the documentation, it is better to handle multiple exceptions through tuples or like this:

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

回答 7

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

A good and simple example for nested try/except could be the following:

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

Now try various combinations and you will get the correct result:

divide(15, 3)
# 5.0

divide(15, 0)
# inf

divide(-15, 0)
# -inf

divide(0, 0)
# nan

[of course we have numpy so we don’t need to create this function]


回答 8

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

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

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

One thing I like to avoid is raising a new exception while handling an old one. It makes the error messages confusing to read.

For example, in my code, I originally wrote

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

And I got this message.

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

What I wanted was this:

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

It doesn’t affect how exceptions are handled. In either block of code, a KeyError would have been caught. This is merely an issue of getting style points.


回答 9

如果将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

If try-except-finally is nested inside finally block, the result from “child” finally is preserved. I have not found official expaination yet, but the following code snippet shows this behavior in 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

回答 10

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

I don’t think it’s a matter of being pythonic or elegant. It’s a matter of preventing exceptions as much as you can. Exceptions are meant to handle errors that might occur in code or events you have no control over. In this case you have full control when checking if an item is an attribute or in a dictionary, so avoid nested exceptions and stick with your second attempt.


声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。