PyObjects From Python Via ctypes

Lawrence D’Oliveiro <[email protected]> Thu, 11 Jun 2026 07:41:20 -0000 (UTC)
Newsgroups comp.lang.python
Organization A noiseless patient Spider
Message-ID <[email protected]>
I see ctypes has a py_object type, but you can’t seem to do much with
it. The following might be more useful:

---- file: pyobjhack.py
import ctypes as ct

def _init_hack() :
    # works out the values of TRACE_REFS and PYOBJECT_HEADER_SIZE.
    global TRACE_REFS, PYOBJECT_HEADER_SIZE
    o = object()
    # CPython implementation detail: “id()” function returns address of object header
    if ct.c_size_t.from_address(id(o)).value == 1 :
        TRACE_REFS = False
        PYOBJECT_HEADER_SIZE = 2
    elif ct.c_size_t.from_address(id(o) + 2 * ct.sizeof(ct.c_void_p)).value == 1 :
        TRACE_REFS = True
        PYOBJECT_HEADER_SIZE = 4
    else :
        raise RuntimeError("cannot determine pyobject header size")
    #end if
      # header of every PyObject has ob_refcnt (type ssize_t) followed by ob_type (type pointer),
      # but these might be preceded by a pair of next/prev pointers if Py_TRACE_REFS is defined.
      # Only way I can figure out to determine which is the case is to construct an object
      # with a known refcount of 1, and see if I can find the value 1 at the start.
    # Confirm that the refcount field behaves as expected:
    o2 = o
    assert ct.c_size_t.from_address(id(o) + (PYOBJECT_HEADER_SIZE - 2) * ct.sizeof(ct.c_void_p)).value == 2
    del o2
    assert ct.c_size_t.from_address(id(o) + (PYOBJECT_HEADER_SIZE - 2) * ct.sizeof(ct.c_void_p)).value == 1
#end _init_hack
_init_hack()
del _init_hack

class PyObject(ct.Structure) :
    "header part of a PyObject structure."

    _fields_ = \
        (
            (
                [],
                [
                    ("_ob_next", ct.c_void_p),
                    ("_ob_prev", ct.c_void_p),
                ],
            )[TRACE_REFS]
        +
            [
                ("ob_refcnt", ct.c_ssize_t),
                ("ob_type", ct.c_void_p),
            ]
        )

    @classmethod
    def from_obj(celf, obj) :
        "aliases the object header to a PyObject struct."
        return \
            celf.from_address(id(obj))
    #end from_obj

#end PyObject