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代码:
ceval.c评估循环部分LOAD_ATTRPyObject_GetAttr()。对于自定义Python类,这最终会调用该_PyObject_GenericGetAttrWithDict()函数(通过type->tp_getattroslot和PyObject_GenericGetAttr。descriptor.__get__()在其上调用)并返回结果。看到这些行是_PyObject_GenericGetAttrWithDict()。__dict__中将属性名称作为键。如果存在这样的密钥,则返回相应的值;否则,返回0。看到这些行。__dict__,但是找到了一个非数据描述符,则将该描述符绑定(__get__在其上调用),并在本节中返回结果。__getattr__在类上定义了一个方法,请调用该方法。请参阅中的这些行slot_tp_getattr_hook,该行是在将__getattr__钩子添加到类时安装的。AttributeError。甲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分配可以在完全不相关的模块中进行。