hasattr()vs try-except块来处理不存在的属性


Answers:


82

hasattr在内部快速执行与该try/except块相同的任务:它是一种非常具体,经过优化的单任务工具,因此,在适用时,应优先选择通用用途的工具。


8
除非您仍然需要try / catch块来处理竞争条件(如果您正在使用线程)。
道格拉斯·里德

1
或者,我刚遇到的特殊情况:没有值的django OneToOneField:hasattr(obj,field_name)返回False,但是有一个具有field_name的属性:它只会引发DidsNotExist错误。
Matthew Schinckel,

3
请注意,这hasattr捕获Python 2.x中的所有异常。请参阅我的答案以获取示例和简单的解决方法。
Martin Geisler 2013年

5
一个有趣的评论try可以表明该操作应该起作用。尽管try并非总是如此,但它很常见,因此可能被认为更具可读性。
Ioannis Filippidis 2014年

88

有没有可以说明性能差异的基准?

时间是你的朋友

$ 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

16
+1用于提供有趣的有形数字。实际上,当“ try”包含普通情况时(例如,Python异常确实很特殊),它是有效的。
Eric O Lebigot 09年

我不确定如何解释这些结果。这里哪个更快,多少?
Stevoisiak

2
@ StevenM.Vascellaro:如果该属性存在,try则速度约为的两倍hasattr()。如果不存在,try则速度要慢大约1.5倍hasattr()(并且两者都比属性确实存在时要慢得多)。这可能是因为,在幸福的道路上,try几乎什么都不做(Python已经在支付异常开销,而不管是否使用它们),但是hasattr()需要名称查找和函数调用。在不幸的道路上,他们俩都必须做一些异常处理和a goto,但是hasattr()要用C而不是Python字节码来完成。
凯文(Kevin)

24

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

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

好处:

  1. getattr没有Martin Geiser指出的不良吞咽行为-在旧的Python中,hasattr甚至会吞下KeyboardInterrupt

  2. 您检查对象是否具有属性的正常原因是为了可以使用该属性,这自然会导致该属性。

  3. 该属性是原子读取的,并且对于其他更改对象的线程是安全的。(但是,如果这是一个主要问题,您可能需要在访问对象之前考虑对其进行锁定。)

  4. 比短try/finally,通常比短hasattr

  5. 一个广泛的except AttributeError障碍可以捕获AttributeErrors您所期望的障碍之外的其他障碍,这可能导致混乱的行为。

  6. 访问属性比访问局部变量要慢(特别是如果它不是普通实例属性时)。(不过,老实说,Python中的微优化通常是傻瓜的事。)

要注意的一件事是,如果您关心obj.attribute设置为None的情况,则需要使用其他哨兵值。


1
+1-与dict.get('my_key','default_value')配合使用,应该广为人知

1
非常适合您要检查存在性并使用具有默认值的属性的常见用例。
dsalaj 2015年

18

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

有问题的情况是,当一个类重写__getattr__hasattr捕获所有异常而不是AttributeError像您期望的那样捕获。换句话说,b: False即使更适合看到ValueError异常,也会打印以下代码:

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.2issue9666)中已修复hasattr现在仅能捕获AttributeError

一个简单的解决方法是编写一个如下所示的实用函数:

_notset = object()

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

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


2
这也提高了位在python2.6的,这样hasattr至少会赶不上KeyboardInterrupt
poolie

或者,而不是safehasattrgetattr如果要使用它,则只需将值复制到一个局部变量中,几乎总是这样。
2013年

@poolie太好了,我不知道这样hasattr做有所改善。
马丁·盖斯勒

是的,这很好。直到今天,我要告诉某人避免使用hasattr,然后去检查时,我还是不知道。我们有一些有趣的bzr错误,其中hasattr吞噬了^ C。
poolie

将2.7升级到3.6时遇到问题。这个答案可以帮助我理解和解决问题。
卡梅什·吉尼

13

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

如果唯一的情况是由于某种错误而导致没有属性的对象,那么我建议您使用异常机制,尽管它可能会更慢,因为我认为这是一种更干净的设计。

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


1
+1表示坚持“尝试”为何对阅读代码的人有意义。:)
Eric O Lebigot 09年

5

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


我认为,这是一个主要问题,已经被很大程度上忽略了。
瑞克(Rick)

5

Sebastian Witowski在EuroPython 2016演讲“编写更快的Python”中涵盖了该主题。这是他的幻灯片的效果摘要。在您进行讨论之前,他还使用术语外观,在此值得一提以标记该关键字。

如果实际上缺少该属性,那么乞求宽恕会比请求权限慢。因此,根据经验,如果知道该属性很可能会丢失或可以预测的其他问题,则可以使用请求权限的方式。否则,如果您期望代码将导致大多数时候可读代码

3许可还是宽恕?

# 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

4

如果这只是您要测试的一个属性,我会说use hasattr。但是,如果您要对可能存在或不存在的属性进行多次访问,则使用try块可以节省一些键入时间。


3

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

python还有一个成语,那就是EAFP(“更容易要求宽恕而不是允许”)比LBYL(“跳前先看”)要好。


2

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

如果要处理当前函数外部某处不存在的属性的情况,则最好使用异常处理。您可能希望使用异常而不是条件的一种指示是,条件仅设置了一个标志并中止了当前操作,其他地方检查了该标志并根据该标志采取了行动。

就是说,正如Rax Olgud所指出的那样,与其他人的交流是代码的重要属性,而您想说“这是一种特殊情况”而不是“这是我期望发生的事情”,这可能更重要。 。


+1表示与条件测试相比,“尝试”可以解释为“这是一种特殊情况”。:)
Eric O Lebigot

0

首先。

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


5
异常在Python中非常常见-每个for语句的末尾都有一个,并且也hasattr使用了一个。但是,“越短越好”(“越简单越好”!)确实适用,因此更简单,更短,更具体的hasattr确实是可取的。
亚历克斯·马丁里

@Alex仅仅是因为Python解析器将那些语句转换为1并不意味着它很普通。他们制作语法糖的原因有一个:因此,您不必担心键入try try块之外的繁琐代码。
未知

如果例外情况是例外,那么“明确性更好”,而原始海报的第二个选项更好,我会说……
Eric O Lebigot 09年

0

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

然而,从这个角度来看,它就变成了选择

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

try: obj.attr
except: ...

因为hasattr只使用第一种情况来确定结果。思考的食物;-)

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.