__getattribute__ 方法和描述符

2022-01-13 00:00:00 python python-internals attributes

问题描述

根据本指南关于 python 描述符https://docs.python.org/howto/descriptor.html

according to this guide on python descriptors https://docs.python.org/howto/descriptor.html

新样式类中的方法对象是使用描述符实现的,以避免在属性查找中对它们进行特殊封装.

method objects in new style classes are implemented using descriptors in order to avoid special casing them in attribute lookup.

我理解的方式是有一个方法对象类型实现了 __get__ 并在使用实例调用时返回绑定的方法对象,在没有实例且仅调用时返回未绑定的方法对象班级.文章还指出,这个逻辑是在 object.__getattribute__ 方法中实现的.像这样:

the way I understand this is that there is a method object type that implements __get__ and returns a bound method object when called with an instance and an unbound method object when called with no instance and only a class. the article also states that this logic is implemented in the object.__getattribute__ method. like so:

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__ 本身就是一个方法!那么它如何绑定到一个对象(没有无限递归)?如果它在属性查找中是特殊大小写的,这不会破坏删除旧样式特殊大小写的目的吗?

however object.__getattribute__ is itself a method! so how is it bound to an object (without infinite recursion)? if it is special cased in the attribute lookup does that not defeat the purpose of removing the old style special casing?


解决方案

实际上,在 CPython 中默认的 __getattribute__ 实现不是 Python 方法,而是在C. 它可以直接访问对象槽(代表 Python 对象的 C 结构中的条目),而无需通过讨厌的属性访问例程.

Actually, in CPython the default __getattribute__ implementation is not a Python method, but is instead implemented in C. It can access object slots (entries in the C structure representing Python objects) directly, without bothering to go through the pesky attribute access routine.

仅仅因为您的 Python 代码必须这样做,并不意味着 C 代码必须这样做.:-)

Just because your Python code has to do this, doesn't mean the C code has to. :-)

如果你确实实现了 Python __getattribute__ 方法,只需使用 object.__getattribute__(self, attrname),或者更好的是,super().__getattribute__(attrname) 以访问 self 上的属性.这样你也不会遇到递归.

If you do implement a Python __getattribute__ method, just use object.__getattribute__(self, attrname), or better still, super().__getattribute__(attrname) to access attributes on self. That way you won't hit recursion either.

在CPython实现中,属性访问实际上是由tp_getattro 槽 在 C 类型对象中,回退到 tp_getattr slot.

In the CPython implementation, the attribute access is actually handled by the tp_getattro slot in the C type object, with a fallback to the tp_getattr slot.

为了详尽并完全公开 C 代码的功能,当您对 实例 使用属性访问时,以下是调用的完整函数集:

To be exhaustive and to fully expose what the C code does, when you use attribute access on an instance, here is the full set of functions called:

  • Python 将属性访问转换为对 <代码>PyObject_GetAttr() C 函数.该函数的实现查找tp_getattrotp_getattr 插槽.

  • Python translates attribute access to a call to the PyObject_GetAttr() C function. The implementation for that function looks up the tp_getattro or tp_getattr slot for your class.

object 类型具有 用 tp_getattro 槽-L1336" rel="nofollow noreferrer">PyObject_GenericGetAttr 函数,它将调用委托给 _PyObject_GenericGetAttrWithDict(*dict 指针设置为 NULLsuppress 参数设置为 0).这个函数是你的 object.__getattribute__ 方法(一个 特殊表名称和插槽之间的映射).

The object type has filled the tp_getattro slot with the PyObject_GenericGetAttr function, which delegates the call to _PyObject_GenericGetAttrWithDict (with the *dict pointer set to NULL and the suppress argument set to 0). This function is your object.__getattribute__ method (a special table maps between the name and the slots).

这个_PyObject_GenericGetAttrWithDict函数可以通过__dict__对象typeobj.html#c.PyTypeObject.tp_dict" rel="nofollow noreferrer">tp_dict slot,但对于 descriptors(包括方法),_PyType_Lookup函数.

This _PyObject_GenericGetAttrWithDict function can access the instance __dict__ object through the tp_dict slot, but for descriptors (including methods), the _PyType_Lookup function is used.

_PyType_Lookup 处理缓存并委托给 find_name_in_mro 缓存未命中;后者查找类(和超类)的属性.该代码使用指向 MRO 中每个类上的 tp_dict 槽的直接指针来引用类属性.

_PyType_Lookup handles caching and delegates to find_name_in_mro on cache misses; the latter looks up attributes on the class (and superclasses). The code uses direct pointers to the tp_dict slot on each class in the MRO to reference class attributes.

如果 _PyType_Lookup 找到一个描述符,它会返回到 _PyObject_GenericGetAttrWithDict 并调用该对象上的 tp_descr_get 函数(__get__ 钩子).

If a descriptor is found by _PyType_Lookup it is returned to _PyObject_GenericGetAttrWithDict and it calls the tp_descr_get function on that object (the __get__ hook).

当您访问 类本身 上的属性时,而不是 _PyObject_GenericGetAttrWithDicttype->tp_getattro 插槽由type_getattro() 函数,它也考虑了元类.此版本也调用 __get__,但将实例参数设置为 None.

When you access an attribute on the class itself, instead of _PyObject_GenericGetAttrWithDict, the type->tp_getattro slot is instead serviced by the type_getattro() function, which takes metaclasses into account too. This version calls __get__ too, but leaves the instance parameter set to None.

此代码无需递归调用 __getattribute__ 即可访问 __dict__ 属性,因为它可以直接访问 C 结构.

Nowhere does this code have to recursively call __getattribute__ to access the __dict__ attribute, as it can simply reach into the C structures directly.

相关文章