如何在C++中实现Python的统一属性查找(__getattribute__),处理描述符与元类场景
如何在C++中实现Python的统一属性查找(getattribute),处理描述符与元类场景
我当初从零用C++写Python解释器时,也在属性查找这块卡了很久——一开始也想拆成实例和类型两个查找函数,但越写越觉得别扭,后来才想通:Python里所有东西都是对象,包括类型本身,所以完全可以用一套统一的逻辑搞定所有场景,核心就是吃透描述符协议和__getattribute__的标准流程。
统一查找的核心原则
不管你访问的是实例的属性(比如3.__str__)还是类型的属性(比如int.__str__),本质上都是对一个PyObject执行属性查找。唯一的区别是:当访问类型对象的属性时,这个对象的ob_type是元类(比如type),而描述符的__get__方法会根据调用上下文(实例访问/类型访问)返回正确的绑定对象。
标准属性查找流程(对应object.__getattribute__)
当执行obj.attr时,统一走以下步骤,不管obj是实例还是类型:
- 获取当前对象的类型:记
T = obj->ob_type(也就是Python中type(obj)的C++实现) - 遍历类型T的MRO(方法解析顺序):
- 按从左到右、从子类到基类的顺序遍历T的所有基类
- 对每个基类
C,检查其__dict__中是否存在attr:
a. 如果找到,取出这个值desc
b. 检查desc是否是描述符(即是否实现了__get__方法):- 如果是数据描述符(同时实现
__get__和__set__):直接调用desc->__get__(obj, T),返回结果,终止整个查找流程 - 如果是非数据描述符(只实现
__get__):暂时记录这个描述符,继续遍历MRO(确保找到优先级最高的定义)
c. 如果不是描述符:暂时记录这个值,继续遍历MRO
- 如果是数据描述符(同时实现
- 检查当前对象自己的
__dict__:- 如果
obj->__dict__中存在attr,直接返回这个值(非数据描述符的优先级低于实例自己的属性)
- 如果
- 处理之前记录的非数据描述符/普通值:
- 如果之前在MRO中找到的是描述符:调用
desc->__get__(instance_arg, owner_arg),其中:- 若
obj是实例对象:instance_arg = obj,owner_arg = T(实例的类型) - 若
obj是类型对象:instance_arg = nullptr,owner_arg = static_cast<PyTypeObject*>(obj)(类型对象本身)
- 若
- 如果是普通值:直接返回这个值
- 如果之前在MRO中找到的是描述符:调用
- 未找到属性:抛出
AttributeError
针对你的四个案例的验证
我们用这套流程逐一对应你提到的四个场景,看看逻辑是否自洽:
案例1:实例的方法查找((3).__str__())
obj是整数实例3,T = int- 遍历
int的MRO,在int.__dict__中找到__str__(非数据描述符,即函数) - 检查
3的__dict__,没有__str__ - 调用
__str__->__get__(3, int),返回绑定了self=3的方法对象,执行后返回字符串"3"
案例2:类型的方法查找(int.__str__())
obj是类型对象int,T = type(int的类型是元类type)- 遍历
type的MRO,在type.__dict__中找到__str__(非数据描述符) - 检查
int的__dict__,没有属于int这个对象的__str__(这里的查找是找类型对象int的属性,不是实例的) - 调用
__str__->__get__(nullptr, int),返回绑定了self=int的方法对象,执行后返回字符串"<class 'int'>"
案例3:实例的__class__属性((3).__class__)
obj是3,T = int- 遍历
int的MRO,在object.__dict__中找到__class__(数据描述符) - 直接调用
__class__->__get__(3, int),返回int类型对象
案例4:类型的__class__属性(int.__class__)
obj是int,T = type- 遍历
type的MRO,找到__class__数据描述符 - 调用
__class__->__get__(nullptr, int),返回type元类对象
C++伪代码实现
基于这个流程,你可以给PyObject添加一个统一的getattribute方法,不需要为类型单独写逻辑:
#include <vector> #include <unordered_map> #include <string> #include <stdexcept> // 前置声明 class PyTypeObject; class PyDescriptor; class PyObject { public: PyTypeObject* ob_type; std::unordered_map<std::string, PyObject*> __dict__; virtual ~PyObject() = default; // 统一的属性查找方法 PyObject* getattribute(const std::string& name) { PyTypeObject* T = this->ob_type; PyObject* found_desc = nullptr; // 步骤2:遍历当前对象类型的MRO for (PyTypeObject* C : T->mro) { auto it = C->__dict__.find(name); if (it != C->__dict__.end()) { PyObject* desc = it->second; // 检查是否是描述符 PyDescriptor* desc_obj = dynamic_cast<PyDescriptor*>(desc); if (desc_obj != nullptr) { if (desc_obj->is_data_descriptor()) { // 数据描述符:直接调用__get__并返回 return desc_obj->__get__(this, T); } else { // 非数据描述符:记录下来,继续找更高优先级的 if (found_desc == nullptr) { found_desc = desc; } } } else { // 普通属性:记录下来 if (found_desc == nullptr) { found_desc = desc; } } } } // 步骤3:检查当前对象自己的__dict__ auto it = this->__dict__.find(name); if (it != this->__dict__.end()) { return it->second; } // 步骤4:处理记录的非数据描述符或普通值 if (found_desc != nullptr) { PyDescriptor* desc_obj = dynamic_cast<PyDescriptor*>(found_desc); if (desc_obj != nullptr) { // 确定__get__的参数 PyObject* instance_arg; PyTypeObject* owner_arg; if (PyTypeObject* type_obj = dynamic_cast<PyTypeObject*>(this)) { // 当前对象是类型,instance参数为nullptr instance_arg = nullptr; owner_arg = type_obj; } else { // 当前对象是实例,instance参数为自身 instance_arg = this; owner_arg = T; } return desc_obj->__get__(instance_arg, owner_arg); } else { // 普通值,直接返回 return found_desc; } } // 步骤5:未找到属性 throw std::runtime_error("AttributeError: '" + name + "' not found"); } }; // 描述符基类 class PyDescriptor : public PyObject { public: virtual PyObject* __get__(PyObject* instance, PyTypeObject* owner) = 0; virtual bool is_data_descriptor() = 0; }; // 类型对象基类 class PyTypeObject : public PyObject { public: std::vector<PyTypeObject*> mro; // 方法解析顺序列表 // 其他类型相关属性(如名称、基类等) }; // 函数对象(非数据描述符) class PyFunctionObject : public PyDescriptor { private: std::string func_name; // 函数实现逻辑... public: PyFunctionObject(const std::string& name) : func_name(name) {} PyObject* __get__(PyObject* instance, PyTypeObject* owner) override { // 简化实现:返回绑定了self的方法对象 class PyBoundMethodObject : public PyObject { public: PyFunctionObject* func; PyObject* self; PyBoundMethodObject(PyFunctionObject* f, PyObject* s) : func(f), self(s) {} // __call__方法实现(这里省略具体逻辑) }; return new PyBoundMethodObject(this, instance ? instance : owner); } bool is_data_descriptor() override { // 函数默认是非数据描述符(没有__set__) return false; } }; // __class__描述符(数据描述符) class PyClassDescriptor : public PyDescriptor { public: PyObject* __get__(PyObject* instance, PyTypeObject* owner) override { if (instance != nullptr) { // 实例的__class__返回其类型 return instance->ob_type; } else { // 类型的__class__返回其元类 return owner->ob_type; } } bool is_data_descriptor() override { // __class__是数据描述符(支持__set__) return true; } };
关键注意点
- 类型也是对象:
PyTypeObject继承自PyObject,所以类型的属性查找和实例完全复用同一套getattribute逻辑,不需要单独写分支。 - 描述符的上下文区分:
__get__的instance参数是核心——当通过实例访问时传实例,通过类型访问时传nullptr,描述符会根据这个返回正确的绑定对象。 - 数据描述符的优先级:数据描述符(如
__class__)的优先级高于实例自己的__dict__,非数据描述符(如普通函数)的优先级低于实例__dict__,这部分一定要按顺序处理。
内容来源于stack exchange
相关产品推荐
相关产品推荐

