[Python] How to implement Python's unified attribute lookup (__getattribute__) in C++,...

Discussão em 'Python' iniciado por Stack, Julho 8, 2025.

  1. Stack

    Stack Membro Participativo

    I'm developing a Python interpreter from scratch in C++ as a hobby project to deepen my understanding of the language's internals.

    I'm currently stuck on implementing the attribute lookup mechanism (what happens during an obj.attr access). I understand the basics: instance attributes are usually in an object's __dict__, while methods and other class attributes are on the object's type and found by traversing the MRO. The behavior of a type's own methods (e.g., int.__str__) is defined on its metaclass (usually type).

    My problem is creating a single, cohesive algorithm in C++ that correctly handles all lookup scenarios, which seem conflicting at first glance.

    The Conflicting Scenarios

    I'm trying to reconcile the logic for the following cases:

    # Case 1: Method on an instance
    # The lookup should find `__str__` on the `int` type and "bind" it
    # to the instance `3` to provide `self`.
    (3).__str__()

    # Case 2: Method on a type (Metaclass)
    # Here, the lookup should happen on the type of `int` (the metaclass, `type`),
    # find the `__str__` defined there, and bind it to the `int` type object.
    # The expected result is the string "<class 'int'>", not the same function from Case 1.
    int.__str__()

    # Case 3: Attribute on an instance
    # The lookup seems to start directly at the `3` object to return its type.
    (3).__class__ # Returns the `int` type object

    # Case 4: Attribute on a type
    # The lookup starts at the `int` object to return its type.
    int.__class__ # Returns the `type` metaclass object


    What I've Tried

    In my C++ implementation, I have base classes PyObject and PyTypeObject. My first attempt was to create two separate find functions, one for instances and another for types, but this feels wrong and doesn't solve the problem in a unified way.

    // Pseudo-code of my flawed attempt
    struct PyObject {
    PyTypeObject* ob_type;
    Scope* scope; // My __dict__

    // Lookup on instances
    virtual PyObject* find(string name) {
    // 1. Check my instance's __dict__ (scope)
    // 2. If not found, search the MRO of my type
    // ...
    }
    };

    struct PyTypeObject : PyObject {
    // Lookup on types (override)
    PyObject* find(string name) override {
    // Different logic that searches the metaclass MRO
    // ...
    }
    };


    This approach doesn't feel right. My research indicates that the key to unifying this is the descriptor protocol (specifically __get__), but I'm struggling to visualize how to integrate it into a single lookup algorithm that works for all cases.

    My Core Question

    My main question is: How can I design a single attribute lookup function (the equivalent of object.__getattribute__ or CPython's tp_getattro) that correctly resolves these conflicting scenarios?

    I'm looking for answers that address:


    1. The correct order of precedence: For an obj.attr access, what is the exact sequence for checking obj.__dict__, type(obj).mro(), and handling data vs. non-data descriptors?


    2. The role of __get__(self, instance, owner): How are the instance and owner arguments used within this unified algorithm to differentiate between a call on an instance (like (3).__str__) and a call on a type (like int.__str__)?


    3. Pseudo-code or a step-by-step algorithm: A sketch of an algorithm that I could use as a reference for my C++ implementation would be incredibly helpful.

    Any explanation, pseudo-code, or pointers to how CPython implements its tp_getattro slot would be immensely helpful. Thank you!

    Continue reading...

Compartilhe esta Página