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

对

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

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

【问题讨论】:

标签: python exception exception-handling attributes hasattr


【解决方案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() 的两倍。如果没有,try 比hasattr() 慢大约 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

优点:

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

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

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

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

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

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

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

【讨论】:

【解决方案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 只是使用第一种情况来确定结果。 值得深思 ;-)

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-01-28
          • 1970-01-01
          • 2017-04-03
          • 2015-09-10
          • 1970-01-01
          • 2021-03-15
          相关资源
          最近更新 更多