Guide to Structs#

  • The structs submodule contains definitions and protocols for interacting with primitive Python objects as C Structs.

1. The ptr type#

You may see a ptr type being used by einspect in lieu of the usual ctypes.pointer() and ctypes.POINTER() types. This type behaves as ctypes.pointer() when called.

from ctypes import pointer
from einspect.types import ptr

ptr(...) == pointer(...)

At type checking time, ptr is a direct alias to ctypes.pointer. You can subscript ptr as you would with ctypes.pointer() to indicate a generic pointer type.

from ctypes import pointer, c_char
from einspect.types import ptr

def foo() -> ptr[c_char]:
    ...

The difference is, ptr lets you do this at runtime as well. For example, ptr[c_char] resolves to a runtime call to ctypes.POINTER(c_char).

from ctypes import pointer, c_char
from einspect.types import ptr

ptr[c_char] == POINTER(c_char)

When using einspect @struct decorators to make new Structs, you can use the ptr type hint to allow both static type checking and the decorator’s runtime type parsing to work.

In other cases there is no difference in using types.ptr or ctypes.pointer / ctypes.POINTER types.

2. Working with PyObject#

Firstly, all PyObject and inherited types are compatible standard ctypes.Structure types. This means they work with ctypes methods as usual.

Converting to and from Python objects#

The following interfaces allow conversion to and from python objects

from einspect.structs import PyObject

x = 100
obj = PyObject.from_object(x)
print(obj) # <PyObject[int] at 0x1060535a0>

base = obj.into_object()
assert base is x

The AsRef trait#

PyObjects also implement the AsRef trait, which provides an as_ref() method that returns a pointer to the object, equivalent to ctypes.pointer(object)

from ctypes import pointer
from einspect.structs import PyObject

obj = PyObject.from_object(5)

print(pointer(obj))
# <einspect.types.LP_PyObject object at 0x10467c750>

print(obj.as_ref())
# <einspect.types.LP_PyObject object at 0x10467c750>

Mutating PyObject attributes#

All modifications to PyObject attributes will directly modify the underlying memory. So these will reflect on the python objects.

For example, let’s add a tuple to itself 🤔

from einspect.structs import PyTupleObject

t = (1, 2)
obj = PyTupleObject.from_object(t)
obj.ob_size = 3
obj.ob_item[2] = obj.as_ref()

print(t)
# (1, 2, (...))
print(t[2][2][2])
# (1, 2, (...))

You can also create PyObject structs first, and then get the python reference.

For example, small integers (-5 to 255) are normally cached in CPython:

x = 1
print(x is 1)
# True

But we can create our own new 1 int

from einspect.structs import PyLongObject, PyTypeObject

st = PyLongObject(
    ob_refcnt=1,
    ob_type=PyTypeObject.from_object(int).as_ref(),
    ob_size=1,
    ob_digit=[1]
)

x = st.into_object()
print(x)
# 1
print(x == 1)
# True
print(x is 1)
# False