关于python:hasattr()vs try except块处理不存在的属性

hasattr() vs try-except block to deal with non-existent attributes

1
2
if hasattr(obj, 'attribute'):
    # do somthing

VS

1
2
3
4
try:
    # access obj.attribute
except AttributeError, e:
    # deal with AttributeError

这应该是首选的和为什么?


有没有什么长凳能说明不同的表演?

时间它是你的朋友

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ python -mtimeit -s 'class C(object): a = 4
c = C()'
'hasattr(c,"nonexistent")'
1000000 loops, best of 3: 1.87 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()'
'hasattr(c,"a")'
1000000 loops, best of 3: 0.446 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()'
'try:
 c.a
except:
 pass'

1000000 loops, best of 3: 0.247 usec per loop
$ python -mtimeit -s 'class C(object): a = 4
c = C()'
'try:
 c.nonexistent
except:
 pass'

100000 loops, best of 3: 3.13 usec per loop
$

       |positive|negative
hasattr|  0.446 |  1.87
try    |  0.247 |  3.13


hasattr在内部和快速执行与try/except块相同的任务:它是一个非常具体、优化的任务工具,因此在适用的情况下,应优先选择非常通用的替代工具。


还有第三种,通常更好的选择:

1
2
3
attr = getattr(obj, 'attribute', None)
if attr is not None:
     print attr

优势:

  • getattr没有马丁·盖瑟(Martin Geiser)指出的不良的异常吞咽行为——在旧的Python中,hasattr甚至会吞下KeyboardInterrupt

  • 您检查对象是否具有属性的正常原因是这样您就可以使用该属性,这自然会导致出现这种情况。

  • 该属性以原子方式读取,并且不受其他线程更改对象的影响。(不过,如果这是一个主要问题,您可能需要考虑在访问对象之前锁定它。)

  • 它比try/finally短,通常比hasattr短。

  • 一个较宽的except AttributeError块可以捕获其他AttributeErrors块,这可能导致行为混乱。

  • 访问一个属性比访问一个局部变量慢(特别是如果它不是一个普通的实例属性)。(不过,老实说,python中的微优化通常是一件蠢事。)

  • 需要注意的一件事是,如果您关心obj.attribute设置为"无"的情况,则需要使用不同的sentinel值。


    我几乎总是使用hasattr:这是大多数情况下的正确选择。

    有问题的情况是,当类重写__getattr__hasattr将捕获所有异常,而不是像您预期的那样只捕获AttributeError。换言之,下面的代码将打印b: False,即使更适合看到ValueError例外:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class X(object):
        def __getattr__(self, attr):
            if attr == 'a':
                return 123
            if attr == 'b':
                raise ValueError('important error from your database')
            raise AttributeError

    x = X()
    print 'a:', hasattr(x, 'a')
    print 'b:', hasattr(x, 'b')
    print 'c:', hasattr(x, 'c')

    重要的错误就这样消失了。这在python 3.2(issue9666)中得到了修复,其中hasattr现在只捕获AttributeError

    一个简单的解决方法是编写这样的实用程序函数:

    1
    2
    3
    4
    _notset = object()

    def safehasattr(thing, attr):
        return getattr(thing, attr, _notset) is not _notset

    这就让getattr处理这种情况,然后它可以引发适当的异常。


    我想说,这取决于函数是否可以通过设计接受没有属性的对象,例如,如果函数有两个调用方,一个提供带有属性的对象,另一个提供没有属性的对象。

    如果您得到一个没有属性的对象的唯一情况是由于某些错误,那么我建议使用异常机制,尽管它可能会慢一些,因为我相信它是一个更干净的设计。

    底线:我认为这是一个设计和可读性问题,而不是一个效率问题。


    如果没有属性不是一个错误条件,异常处理变量有一个问题:它还将捕获访问obj.attribute时可能在内部出现的attributeErrors(例如,因为属性是一个属性,所以访问它会调用一些代码)。


    如果只是您要测试的一个属性,我会说使用hasattr。但是,如果您对可能存在或不存在的属性进行了多次访问,那么使用try块可能会节省一些输入。


    我建议选择2。如果其他线程正在添加或删除属性,则选项1具有争用条件。

    另外,python还有一个成语,EAFP("请求宽恕比允许容易")比LBYL("三思而后行")更好。


    这一主题被塞巴斯蒂安·维托夫斯基在《欧洲之声2016》中报道。这是他的幻灯片的复制品和性能摘要。他还使用术语look,在您开始讨论之前,这里值得一提的是标记该关键字。

    If the attribute is actually missing then begging for forgiveness will
    be slower than asking for permissions. So as a rule of thumb you can
    use the ask for permission way if know that it is very likely that the
    attribute will be missing or other problems that you can predict.
    Otherwise if you expect code will result in most of the times readable
    code

    3许可还是宽恕?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    # CASE 1 -- Attribute Exists
    class Foo(object):
        hello = 'world'
    foo = Foo()

    if hasatter(foo, 'hello'):
        foo.hello
    ## 149ns ##

    try:
        foo.hello
    except AttributeError:
        pass
    ## 43.1 ns ##
    ## 3.5 times faster


    # CASE 2 -- Attribute Absent
    class Bar(object):
        pass
    bar = Bar()

    if hasattr(bar, 'hello'):
        bar.hello
    ## 428 ns ##

    try:
        bar.hello
    except AttributeError :
        pass
    ## 536 ns ##
    ## 25% slower

    从实践的角度来看,在大多数语言中,使用条件总是比处理异常快得多。

    如果您想要处理当前函数之外某个地方不存在的属性的情况,那么异常是更好的方法。您可能希望使用异常而不是条件的指示器是,条件仅设置标志并中止当前操作,而其他地方的某些项检查此标志并基于此采取操作。

    也就是说,正如Rax Olgud指出的,与他人的交流是代码的一个重要属性,你想说的"这是一个特殊情况"而不是"这是我期望发生的事情"可能更重要。


    至少当它取决于程序中正在发生的事情时,忽略了可读性等人类的部分(事实上大多数时候它比性能更重要(至少在这种情况下-有这样的性能跨度),正如Roee Adler和其他人指出的那样)。

    尽管如此,从这个角度来看,这就变成了一个选择

    1
    2
    try: getattr(obj, attr)
    except: ...

    1
    2
    try: obj.attr
    except: ...

    因为hasattr只使用第一个案例来确定结果。思想的食物;—)


    第一。

    越短越好。例外情况应该是例外。