Python中的hasattr与getattr

17 投票
2 回答
29097 浏览
提问于 2025-04-18 14:52

最近我在看一些推特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的实现代码,发现hasattrgetattr这两个函数似乎都调用了下一个函数:

v = PyObject_GetAttr(v, name);

但是getattr的代码比hasattr多了很多,这可能导致它更慢。

有没有人知道为什么文档里说hasattr会调用getattr,而我们似乎在鼓励用户使用getattr而不是hasattr,尽管这样做在性能上并不划算?这只是因为getattr看起来更“Python风格”吗?

也许我在测试中做错了什么 :)

谢谢,

Raúl

2 个回答

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
>>>
24

文档并没有特别鼓励使用这个方法,它只是说了一些显而易见的事情。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'
>>>

撰写回答