__getattribute__ 方法和描述符
根据这篇关于Python描述符的指南,https://docs.python.org/howto/descriptor.html
在新风格的类中,方法对象是通过描述符来实现的,这样可以避免在查找属性时需要特别处理它们。
我理解的意思是,有一种方法对象类型,它实现了__get__
这个功能。当你用一个实例去调用它时,它会返回一个绑定的方法对象;而当你不使用实例,只用类去调用时,它会返回一个未绑定的方法对象。文章还提到,这个逻辑是在object.__getattribute__
方法中实现的。就像这样:
def __getattribute__(self, key):
"Emulate type_getattro() in Objects/typeobject.c"
v = object.__getattribute__(self, key)
if hasattr(v, '__get__'):
return v.__get__(None, self)
return v
不过,object.__getattribute__
本身也是一个方法!那么它是怎么和一个对象绑定在一起的呢(而不会导致无限循环)?如果它在属性查找中被特别处理,那不是就违背了去掉旧风格特殊处理的目的了吗?
1 个回答
其实,在CPython中,默认的__getattribute__
实现并不是用Python写的,而是用C语言实现的。它可以直接访问对象的槽位(也就是表示Python对象的C结构中的条目),而不需要经过麻烦的属性访问流程。
虽然你的Python代码需要这样做,但C代码不需要这么麻烦。:-)
如果你自己实现了一个Python的__getattribute__
方法,建议使用object.__getattribute__(self, attrname)
,或者更好的是,使用super().__getattribute__(attrname)
来访问self
上的属性。这样你就不会陷入递归调用的问题了。
在CPython的实现中,属性访问实际上是通过C类型对象中的tp_getattro
槽位来处理的,如果没有找到,就会退回到tp_getattr
槽位。
为了更全面地展示C代码的工作原理,当你在一个实例上使用属性访问时,实际上会调用一系列的函数,具体如下:
Python会把属性访问转换为调用
PyObject_GetAttr()
这个C函数。这个函数的实现会查找你类的tp_getattro
或tp_getattr
槽位。object
类型已经填充了tp_getattro
槽位,使用的是PyObject_GenericGetAttr
函数,这个函数会调用_PyObject_GenericGetAttrWithDict
(其中*dict
指针设置为NULL
,suppress
参数设置为0
)。这个函数就是你的object.__getattribute__
方法(有一个特殊表将名称和槽位对应起来)。这个
_PyObject_GenericGetAttrWithDict
函数可以通过tp_dict
槽位访问实例的__dict__
对象,但对于描述符(包括方法),则使用_PyType_Lookup
函数。_PyType_Lookup
负责缓存,并在缓存未命中时调用find_name_in_mro
;后者会在类(及其父类)上查找属性。代码直接使用指向每个类的tp_dict
槽位的指针来引用类属性。如果通过
_PyType_Lookup
找到了一个描述符,它会被返回给_PyObject_GenericGetAttrWithDict
,然后这个函数会调用该对象的tp_descr_get
函数(也就是__get__
钩子)。
当你在类本身上访问属性时,type->tp_getattro
槽位会由type_getattro()
函数来处理,这个函数也会考虑元类。这个版本也会调用__get__
,但将实例参数设置为None
。
在这个代码中,没有任何地方需要递归调用__getattribute__
来访问__dict__
属性,因为它可以直接访问C结构。