考虑这段代码:
import timeit
import dis
class Bob(object):
__slots__ = "_a",
def __init__(self):
self._a = "a"
@property
def a_prop(self):
return self._a
bob = Bob()
def return_attribute():
return bob._a
def return_property():
return bob.a_prop
print(dis.dis(return_attribute))
print(dis.dis(return_property))
print("attribute:")
print(timeit.timeit("return_attribute()",
setup="from __main__ import return_attribute", number=1000000))
print("@property:")
print(timeit.timeit("return_property()",
setup="from __main__ import return_property", number=1000000))
很容易看出 return_attribute
和 return_property
产生相同的字节码:
17 0 LOAD_GLOBAL 0 (bob)
3 LOAD_ATTR 1 (_a)
6 RETURN_VALUE
None
20 0 LOAD_GLOBAL 0 (bob)
3 LOAD_ATTR 1 (a_prop)
6 RETURN_VALUE
None
但是,时间不同:
attribute:
0.106526851654
@property:
0.210631132126
为什么?
最佳答案
属性作为函数调用 执行,而属性查找仅仅是哈希表(字典)查找。所以是的,那总是会更慢。
LOAD_ATTR
字节码在这里不是定时操作。您缺少的是 LOAD_ATTR
将属性查找委托(delegate)给对象类型;通过触发 C 代码:
- 查找堆栈顶部对象的类型,并要求它为给定实例的属性生成值。查看
ceval.c
evaluation loop section forLOAD_ATTR
,调用PyObject_GetAttr()
.对于自定义 Python 类,这最终会调用_PyObject_GenericGetAttrWithDict()
function (通过type->tp_getattro
slot 和PyObject_GenericGetAttr
。 - 该对象然后通过其自己的 MRO 查找任何与属性名称匹配的描述符对象。如果存在这样的对象,并且它是数据描述符,则搜索停止并绑定(bind)数据描述符(在其上调用
descriptor.__get__()
)并返回结果。参见 these lines is_PyObject_GenericGetAttrWithDict()
. - 如果没有数据描述符,则查看实例
__dict__
以获取属性名称作为键。如果存在这样的键,则返回相应的值;见these lines . - 如果实例
__dict__
中没有这样的键,但是找到了一个非数据描述符,那么该描述符就被绑定(bind)了(__get__
被调用) ,结果返回,在this section . - 如果在类上定义了一个
__getattr__
方法,调用该方法。参见 these lines inslot_tp_getattr_hook
,当您将__getattr__
Hook 添加到类时安装。 - 否则,引发
AttributeError
。
property
对象是数据描述符;它不仅实现了 __get__
,还实现了 __set__
和 __delete__
方法。使用实例在 property
上调用 __get__
会导致 property
对象调用已注册的 getter 函数。
参见 Descriptor HOWTO有关描述符的更多信息,以及 Invoking Descriptors section Python 数据模型文档。
字节码没有区别,因为它不是由LOAD_ATTR
字节码来决定属性是属性还是常规属性。 Python 是一种动态语言,编译器不能事先知道访问的属性是否是一个属性。您可以随时更改您的类(class):
class Foo:
def __init__(self):
self.bar = 42
f = Foo()
print(f.bar) # 42
Foo.bar = property(lambda self: 81)
print(f.bar) # 81
在上面的示例中,当您从 bar
名称开始时,该名称仅作为 Foo
类的 f
实例的属性存在,通过添加 Foo.bar
property
对象,我们拦截了名称 bar
的查找过程,因为 property
是一个数据描述符,因此可以覆盖任何实例查找。 但 Python 无法提前知道这一点,因此无法为属性查找提供不同的字节码。例如,Foo.bar
赋值可能发生在完全不相关的模块中。
关于python - 为什么@property 比属性慢,而字节码是相同的,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57055758/