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

Posted

技术标签:

【中文标题】hasattr() 与 try-except 块处理不存在的属性【英文标题】:hasattr() vs try-except block to deal with non-existent attributes 【发布时间】:2010-10-28 12:52:51 【问题描述】:
if hasattr(obj, 'attribute'):
    # do somthing

try:
    # access obj.attribute
except AttributeError, e:
    # deal with AttributeError

应该首选哪个以及为什么?

【问题讨论】:

复制:***.com/questions/598157/…, ***.com/questions/610883/… 【参考方案1】:

有没有可以说明性能差异的长凳?

是你的朋友了

$ 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

【讨论】:

+1 用于提供有趣、有形的数字。事实上,当它包含常见情况时(即当 Python 异常确实异常时),“try”是有效的。 我不确定如何解释这些结果。这里哪个更快,速度快多少? @StevenM.Vascellaro:如果属性存在,try 的速度大约是hasattr() 的两倍。如果没有,tryhasattr() 慢大约 1.5 倍(并且两者都比属性确实存在时慢得多)。这可能是因为,在幸福的道路上,try 几乎没有做任何事情(Python 已经为异常的开销付出了代价,无论您是否使用它们),但hasattr() 需要名称查找和函数调用。在不愉快的道路上,他们都必须做一些异常处理和goto,但hasattr() 用C 而不是Python 字节码。【参考方案2】:

hasattr 在内部快速执行与try/except 块相同的任务:它是一个非常具体、经过优化的单任务工具,因此在适用时应优先于非常通用的替代方案。

【讨论】:

除非您仍然需要 try/catch 块来处理竞争条件(如果您正在使用线程)。 或者,我刚刚遇到的特殊情况:一个没有值的 django OneToOneField:hasattr(obj, field_name) 返回 False,但是有一个带有 field_name 的属性:它只是引发了一个 DoesNotExist 错误。 请注意,hasattr 将在 Python 2.x 中捕获所有异常。有关示例和简单的解决方法,请参阅 my answer。 一个有趣的comment:try 可以传达操作应该有效。尽管try 的意图并不总是这样,但它很常见,因此可能被认为更具可读性。【参考方案3】:

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

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

优点:

    getattr 没有坏的 exception-swallowing behavior pointed out by Martin Geiser - 在旧 Python 中,hasattr 甚至会吞下 KeyboardInterrupt

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

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

    它比try/finally 短,而且通常比hasattr 短。

    一个宽泛的except AttributeError 块可能会捕获其他AttributeErrors 而不是您所期望的,这可能会导致令人困惑的行为。

    访问属性比访问局部变量慢(尤其是当它不是普通的实例属性时)。 (不过,说实话,Python 中的微优化通常是徒劳的。)

需要注意的一点是,如果您关心 obj.attribute 设置为 None 的情况,则需要使用不同的标记值。

【讨论】:

+1 - 这与 dict.get('my_key', 'default_value') 一致,应该更广为人知 非常适合您想要检查存在性并使用具有默认值的属性的常见用例。 不需要if attr is not None:。只有if attr: 就足够了。 @demberto if attr is sufficient only if you don't care to distinguish between the attribute being unset/None, or it being set to 0, false, or other values that are false in a boolean context In [1]: class Foo(): ...: def foo(self): ...: return getattr(self, 'x', False) ...: In [2]: f = Foo() In [3]: f.foo() Out[3]: False In [4]: f.x = 1 In [5]: f.foo() Out[5]: 1 太棒了!【参考方案4】:

我几乎总是使用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')

重要的错误就这样消失了。这是fixed in Python 3.2 (issue9666),其中hasattr 现在只捕获AttributeError

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

_notset = object()

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

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

【讨论】:

这也是 Python2.6 中的 improved a bit,因此 hasattr 至少不会捕获 KeyboardInterrupt 等。 或者,如果你要使用它,而不是safehasattr,只需使用getattr 将值复制到一个局部变量中,你几乎总是这样。 @poolie 太好了,我不知道hasattr 被这样改进了。 是的,很好。我也不知道,直到今天我正要告诉某人避开hasattr,然后去检查。我们遇到了一些有趣的 bzr 错误,其中 hasattr 刚刚吞下了 ^C。 在将 2.7 升级到 3.6 时遇到问题。这个答案帮助我理解和解决问题。【参考方案5】:

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

如果你得到一个没有属性的对象的唯一情况是由于一些错误,我建议使用异常机制,即使它可能会更慢,因为我相信它是一个更简洁的设计。

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

【讨论】:

+1 坚持为什么“尝试”对阅读代码的人有意义。 :)【参考方案6】:

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

【讨论】:

在我看来,这是一个在很大程度上被忽视的主要问题。【参考方案7】:

Sebastian Witowski 在 EuroPython 2016 演讲Writing faster Python 中讨论了这个主题。这是他的幻灯片和性能摘要的复制品。在此讨论中,他还使用了术语look before you jump,在此值得一提以标记该关键字。

如果该属性确实缺失,则乞求宽恕 比请求权限慢。因此,根据经验,您可以 如果知道很可能 属性将丢失或您可以预测的其他问题。 否则,如果您期望代码将导致大部分时间可读 代码

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

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

我建议选项 2。如果其他线程正在添加或删除该属性,选项 1 会出现竞争条件。

python 也有一个Idiom,即 EAFP(“请求宽恕比请求许可更容易”)比 LBYL(“先看再跳”)更好。

【讨论】:

【参考方案10】:

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

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

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

【讨论】:

+1 表示与条件测试相比,坚持“尝试”可以解释为“这是一种特殊情况”。 :)【参考方案11】:

第一个。

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

【讨论】:

异常在 Python 中很常见——每个for 语句的末尾都有一个,hasattr 也使用一个。但是,“越短越好”(并且“越简单越好”!)确实适用,因此更简单、更短、更具体的 hasattr 确实更可取。 @Alex 仅仅因为 Python 解析器将这些语句转换为 1 并不意味着它很常见。他们制作这种语法糖是有原因的:这样您就不会被键入 try except 块的笨拙所困扰。 如果异常是异常的,那么“显式更好”,而原始发​​布者的第二个选项更好,我会说......【参考方案12】:

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

尽管如此,从这个角度来看, 然后就变成了选择的问题

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

try: obj.attr
except: ...

因为hasattr 只是使用第一种情况来确定结果。 值得深思 ;-)

【讨论】:

以上是关于hasattr() 与 try-except 块处理不存在的属性的主要内容,如果未能解决你的问题,请参考以下文章

try-except语句与else子句联合使用处理可能出现的程序异常

程序测试与爬虫

使用带有try-except块的python“with”语句

python之ftp与paramiko与hasattr与getattr

Python之hasattr,getattr与setattr的使用

python反射hasattr getattr setattr delattr