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()
的两倍。如果没有,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
优点:
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