Python中的hasattr与getattr
最近我在看一些推特和Python文档,里面提到了一个叫做hasattr的东西,它的意思是:
hasattr(对象, 属性名)
这个函数需要两个参数,一个是对象,另一个是字符串(属性名)。如果这个字符串是对象的一个属性名,返回True;如果不是,返回False。(这个过程是通过调用getattr(对象, 属性名)来实现的,然后看看是否会出现AttributeError错误。)
在Python中有一句话,意思是“请求原谅比请求许可更容易”,我通常是赞同这个观点的。
我尝试用一段非常简单的Python代码来做性能测试:
import timeit
definition="""\
class A(object):
a = 1
a = A()
"""
stm="""\
hasattr(a, 'a')
"""
print timeit.timeit(stmt=stm, setup=definition, number=10000000)
stm="""\
getattr(a, 'a')
"""
print timeit.timeit(stmt=stm, setup=definition, number=10000000)
测试结果是:
$ python test.py
hasattr(a, 'a')
1.26515984535
getattr(a, 'a')
1.32518696785
我还试着看看如果属性不存在会发生什么,结果发现getattr和hasattr之间的差别更大。我看到的情况是,getattr比hasattr慢,但文档里说hasattr会调用getattr。
我查了CPython的实现代码,发现hasattr和getattr这两个函数似乎都调用了下一个函数:
v = PyObject_GetAttr(v, name);
但是getattr的代码比hasattr多了很多,这可能导致它更慢。
有没有人知道为什么文档里说hasattr会调用getattr,而我们似乎在鼓励用户使用getattr而不是hasattr,尽管这样做在性能上并不划算?这只是因为getattr看起来更“Python风格”吗?
也许我在测试中做错了什么 :)
谢谢,
Raúl
2 个回答
看起来 hasattr
这个函数在处理异常时有点问题(至少在 Python 2.7 中是这样),所以在它修复之前,最好还是不要使用它。
举个例子,看看 下面的代码:
>>> class Foo(object):
... @property
... def my_attr(self):
... raise ValueError('nope, nope, nope')
...
>>> bar = Foo()
>>> bar.my_attr
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in my_attr
ValueError: nope, nope, nope
>>> hasattr(Foo, 'my_attr')
True
>>> hasattr(bar, 'my_attr')
False
>>> getattr(bar, 'my_attr', None)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in my_attr
ValueError: nope, nope, nope
>>>
文档并没有特别鼓励使用这个方法,它只是说了一些显而易见的事情。hasattr
这个函数是这样实现的,如果一个属性的获取方法抛出 AttributeError
错误,就会让人觉得这个属性根本不存在。这是一个很重要的细节,所以文档里特别提到了这一点。举个例子,看看下面这段代码:
class Spam(object):
sausages = False
@property
def eggs(self):
if self.sausages:
return 42
raise AttributeError("No eggs without sausages")
@property
def invalid(self):
return self.foobar
spam = Spam()
print(hasattr(Spam, 'eggs'))
print(hasattr(spam, 'eggs'))
spam.sausages = True
print(hasattr(spam, 'eggs'))
print(hasattr(spam, 'invalid'))
结果是
True
False
True
False
这里的 Spam
类有一个名为 eggs
的属性描述符,但如果 not self.sausages
的话,获取这个属性时会抛出 AttributeError
错误,因此这个类的实例就会认为没有 eggs
这个属性。
除此之外,只有在你不需要这个属性的值时才使用 hasattr
;如果你需要这个值,应该用 getattr
,并传入两个参数,然后捕获异常,或者传入三个参数,第三个参数是一个合理的默认值。
使用 getattr()
的结果(2.7.9):
>>> spam = Spam()
>>> print(getattr(Spam, 'eggs'))
<property object at 0x01E2A570>
>>> print(getattr(spam, 'eggs'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 7, in eggs
AttributeError: No eggs without sausages
>>> spam.sausages = True
>>> print(getattr(spam, 'eggs'))
42
>>> print(getattr(spam, 'invalid'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 10, in invalid
AttributeError: 'Spam' object has no attribute 'invalid'
>>>