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

iva*_*ult 3 python performance cpython properties 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))

Run Code Online (Sandbox Code Playgroud)

很容易看到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
Run Code Online (Sandbox Code Playgroud)

但是,时间有所不同:

 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
Run Code Online (Sandbox Code Playgroud)

为什么?

Mar*_*ers 10

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

该LOAD_ATTR字节码是不是在这里一个固定时间操作。您所缺少的是LOAD_ATTR 将属性查找委托给对象类型。通过触发以下C代码:

甲property对象是一个数据描述符; 它不仅实现,__get__而且实现__set__和__delete__方法。调用__get__上property用一个实例使property对象调用注册的getter函数。

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

字节码没有什么不同,因为它不是由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
Run Code Online (Sandbox Code Playgroud)

在上面的示例中,虽然您bar仅以名称作为f类实例的属性存在Foo,但通过添加Foo.bar property对象,我们拦截了名称的查找过程bar,因为a property是数据描述符,因此可以覆盖所有实例查找。 但是Python无法事先知道这一点,因此无法为属性查找提供不同的字节码。例如,Foo.bar分配可以在完全不相关的模块中进行。