为什么@property在字节码相同的情况下会使属性变慢

时间:2019-07-16 11:01:33

标签: python performance properties cpython python-internals

考虑以下这段代码:

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

为什么?

1 个答案:

答案 0 :(得分:8)

将属性作为函数调用执行,而属性查找只是哈希表(字典)查找。是的,那总是会慢一些。

此处的LOAD_ATTR字节码不是固定时间的操作。您缺少的是LOAD_ATTR 将属性查找委托给对象类型;通过触发以下C代码:

property对象是数据描述符;它不仅实现__get__,而且实现__set____delete__方法。使用实例在__get__上调用property会导致property对象调用已注册的getter函数。

有关描述符以及Python数据模型描述的Descriptor HOWTO的更多信息,请参见Invoking Descriptors section

字节码没什么不同,因为不能由LOAD_ATTR字节码来决定属性是属性还是常规属性。 Python是一种动态语言,如果访问的属性将是属性,则编译器无法预先知道。您可以随时更改课程

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名称开头时,通过添加f {{仅在类Foo的{​​{1}}实例上作为属性存在。 1}}对象我们截取了名称Foo.bar的查找过程,因为property是数据描述符,因此可以覆盖所有实例查找。 但是Python无法事先知道这一点,因此无法为属性查找提供不同的字节码。例如,bar分配可能发生在一个完全不相关的模块中。