You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++中实现Python的统一属性查找(__getattribute__),处理描述符与元类场景

如何在C++中实现Python的统一属性查找(getattribute),处理描述符与元类场景

我当初从零用C++写Python解释器时,也在属性查找这块卡了很久——一开始也想拆成实例和类型两个查找函数,但越写越觉得别扭,后来才想通:Python里所有东西都是对象,包括类型本身,所以完全可以用一套统一的逻辑搞定所有场景,核心就是吃透描述符协议和__getattribute__的标准流程。

统一查找的核心原则

不管你访问的是实例的属性(比如3.__str__)还是类型的属性(比如int.__str__),本质上都是对一个PyObject执行属性查找。唯一的区别是:当访问类型对象的属性时,这个对象的ob_type是元类(比如type),而描述符的__get__方法会根据调用上下文(实例访问/类型访问)返回正确的绑定对象。

标准属性查找流程(对应object.__getattribute__)

当执行obj.attr时,统一走以下步骤,不管obj是实例还是类型:

  1. 获取当前对象的类型:记T = obj->ob_type(也就是Python中type(obj)的C++实现)
  2. 遍历类型T的MRO(方法解析顺序):
    • 按从左到右、从子类到基类的顺序遍历T的所有基类
    • 对每个基类C,检查其__dict__中是否存在attr:
      a. 如果找到,取出这个值desc
      b. 检查desc是否是描述符(即是否实现了__get__方法):
      • 如果是数据描述符(同时实现__get__和__set__):直接调用desc->__get__(obj, T),返回结果,终止整个查找流程
      • 如果是非数据描述符(只实现__get__):暂时记录这个描述符,继续遍历MRO(确保找到优先级最高的定义)
        c. 如果不是描述符:暂时记录这个值,继续遍历MRO
  3. 检查当前对象自己的__dict__:
    • 如果obj->__dict__中存在attr,直接返回这个值(非数据描述符的优先级低于实例自己的属性)
  4. 处理之前记录的非数据描述符/普通值:
    • 如果之前在MRO中找到的是描述符:调用desc->__get__(instance_arg, owner_arg),其中:
      • 若obj是实例对象:instance_arg = obj,owner_arg = T(实例的类型)
      • 若obj是类型对象:instance_arg = nullptr,owner_arg = static_cast<PyTypeObject*>(obj)(类型对象本身)
    • 如果是普通值:直接返回这个值
  5. 未找到属性:抛出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;
    }
};

关键注意点

  1. 类型也是对象:PyTypeObject继承自PyObject,所以类型的属性查找和实例完全复用同一套getattribute逻辑,不需要单独写分支。
  2. 描述符的上下文区分:__get__的instance参数是核心——当通过实例访问时传实例,通过类型访问时传nullptr,描述符会根据这个返回正确的绑定对象。
  3. 数据描述符的优先级:数据描述符(如__class__)的优先级高于实例自己的__dict__,非数据描述符(如普通函数)的优先级低于实例__dict__,这部分一定要按顺序处理。

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.07 10:29:29