Skip to main content

Overview

Type objects (PyTypeObject) define the behavior of Python types. Every Python object has a type, accessible via obj->ob_type or Py_TYPE(obj).

Built-in Type Objects

Python provides type objects for all built-in types:
PyTypeObject PyType_Type;        // type
PyTypeObject PyLong_Type;        // int
PyTypeObject PyFloat_Type;       // float
PyTypeObject PyUnicode_Type;     // str
PyTypeObject PyList_Type;        // list
PyTypeObject PyDict_Type;        // dict
PyTypeObject PyTuple_Type;       // tuple
PyTypeObject PySet_Type;         // set
PyTypeObject PyBool_Type;        // bool
PyTypeObject PyBytes_Type;       // bytes

Type Checking

PyType_Check

int PyType_Check(PyObject *o)
Check if object is a type object. Returns: 1 if type, 0 otherwise

PyType_CheckExact

int PyType_CheckExact(PyObject *o)
Check if object is exactly type (not a subclass). Returns: 1 if exact match, 0 otherwise

Py_IS_TYPE

int Py_IS_TYPE(PyObject *o, PyTypeObject *type)
Check if object’s type is exactly type. Example:
if (Py_IS_TYPE(obj, &PyLong_Type)) {
    // obj is exactly int, not a subclass
}

PyObject_TypeCheck

int PyObject_TypeCheck(PyObject *o, PyTypeObject *type)
Check if object is of type or subtype. Example:
if (PyObject_TypeCheck(obj, &PyList_Type)) {
    // obj is list or list subclass
    PyListObject *list = (PyListObject*)obj;
}

Type Operations

PyType_IsSubtype

int PyType_IsSubtype(PyTypeObject *a, PyTypeObject *b)
Check if type a is a subtype of type b.
a
PyTypeObject*
required
Potential subtype
b
PyTypeObject*
required
Potential supertype
Returns: 1 if a is subtype of b, 0 otherwise

PyType_Ready

int PyType_Ready(PyTypeObject *type)
Initialize a type object, filling in necessary fields.
Call PyType_Ready() before using a custom type object. Usually done in module initialization.
Returns: 0 on success, -1 on error

PyType_GetName

PyObject* PyType_GetName(PyTypeObject *type)
Get the short name of the type. Returns: New reference to string

PyType_GetQualName

PyObject* PyType_GetQualName(PyTypeObject *type)
Get the qualified name of the type. Returns: New reference to string

Defining Custom Types

Basic Type Definition

To define a new Python type in C:
#include <Python.h>
#include <structmember.h>

// 1. Define the C structure
typedef struct {
    PyObject_HEAD
    PyObject *name;    // Managed attributes
    int value;
} MyObject;

// 2. Destructor
static void MyObject_dealloc(MyObject *self) {
    Py_XDECREF(self->name);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

// 3. Constructor
static PyObject* MyObject_new(
    PyTypeObject *type,
    PyObject *args,
    PyObject *kwds
) {
    MyObject *self;
    self = (MyObject*)type->tp_alloc(type, 0);
    if (self != NULL) {
        self->name = PyUnicode_FromString("");
        if (self->name == NULL) {
            Py_DECREF(self);
            return NULL;
        }
        self->value = 0;
    }
    return (PyObject*)self;
}

// 4. Initialization
static int MyObject_init(
    MyObject *self,
    PyObject *args,
    PyObject *kwds
) {
    static char *kwlist[] = {"name", "value", NULL};
    PyObject *name = NULL;
    int value = 0;
    
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oi", kwlist,
                                     &name, &value))
        return -1;
    
    if (name) {
        PyObject *tmp = self->name;
        Py_INCREF(name);
        self->name = name;
        Py_XDECREF(tmp);
    }
    
    self->value = value;
    return 0;
}

// 5. Type object definition
static PyTypeObject MyType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "mymodule.MyObject",
    .tp_doc = "Custom MyObject type",
    .tp_basicsize = sizeof(MyObject),
    .tp_itemsize = 0,
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_new = MyObject_new,
    .tp_init = (initproc)MyObject_init,
    .tp_dealloc = (destructor)MyObject_dealloc,
};

// 6. Module initialization
PyMODINIT_FUNC PyInit_mymodule(void) {
    PyObject *m;
    
    if (PyType_Ready(&MyType) < 0)
        return NULL;
    
    m = PyModule_Create(&mymodule);
    if (m == NULL)
        return NULL;
    
    Py_INCREF(&MyType);
    if (PyModule_AddObject(m, "MyObject", (PyObject*)&MyType) < 0) {
        Py_DECREF(&MyType);
        Py_DECREF(m);
        return NULL;
    }
    
    return m;
}

Type Object Structure

PyTypeObject Fields

Key fields in PyTypeObject:
tp_name
const char*
Type name (should include module name: "module.TypeName")
tp_basicsize
Py_ssize_t
Size of instance in bytes (sizeof(YourStruct))
tp_itemsize
Py_ssize_t
Size of variable-length items (0 for fixed-size types)
tp_doc
const char*
Type docstring
tp_flags
unsigned long
Type flags (see below)
tp_new
newfunc
Object creation function (__new__)
tp_init
initproc
Object initialization function (__init__)
tp_dealloc
destructor
Object destruction function
tp_repr
reprfunc
__repr__ implementation
tp_str
reprfunc
__str__ implementation
tp_methods
PyMethodDef*
Method definitions
tp_members
PyMemberDef*
Member (attribute) definitions
tp_getset
PyGetSetDef*
Computed attribute definitions

Type Flags

Common type flags:
  • Py_TPFLAGS_DEFAULT - Default flags (always use)
  • Py_TPFLAGS_BASETYPE - Type can be subclassed
  • Py_TPFLAGS_HEAPTYPE - Type allocated on heap
  • Py_TPFLAGS_HAVE_GC - Type participates in garbage collection

Adding Methods

Method Definitions

static PyObject* MyObject_method(MyObject *self, PyObject *args) {
    // Method implementation
    Py_RETURN_NONE;
}

static PyMethodDef MyObject_methods[] = {
    {"method", (PyCFunction)MyObject_method, METH_VARARGS,
     "Method docstring"},
    {NULL}  // Sentinel
};

// Add to type object:
static PyTypeObject MyType = {
    // ...
    .tp_methods = MyObject_methods,
};

Adding Members

Direct Attribute Access

static PyMemberDef MyObject_members[] = {
    {"value", T_INT, offsetof(MyObject, value), 0,
     "integer value"},
    {"name", T_OBJECT_EX, offsetof(MyObject, name), 0,
     "name string"},
    {NULL}  // Sentinel
};

// Add to type object:
static PyTypeObject MyType = {
    // ...
    .tp_members = MyObject_members,
};
Member types:
  • T_INT - C int
  • T_LONG - C long
  • T_DOUBLE - C double
  • T_STRING - C string (char*)
  • T_OBJECT - PyObject* (can be NULL)
  • T_OBJECT_EX - PyObject* (raises AttributeError if NULL)
  • T_BOOL - C char (boolean)

Adding Properties

Computed Attributes

static PyObject* MyObject_getvalue(MyObject *self, void *closure) {
    return PyLong_FromLong(self->value);
}

static int MyObject_setvalue(MyObject *self, PyObject *value, void *closure) {
    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError, "Cannot delete value attribute");
        return -1;
    }
    
    if (!PyLong_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "value must be an integer");
        return -1;
    }
    
    self->value = PyLong_AsLong(value);
    if (self->value == -1 && PyErr_Occurred())
        return -1;
    
    return 0;
}

static PyGetSetDef MyObject_getsetters[] = {
    {"value", (getter)MyObject_getvalue, (setter)MyObject_setvalue,
     "value property", NULL},
    {NULL}  // Sentinel
};

// Add to type object:
static PyTypeObject MyType = {
    // ...
    .tp_getset = MyObject_getsetters,
};

Special Methods

Comparison

static PyObject* MyObject_richcompare(
    PyObject *self,
    PyObject *other,
    int op
) {
    MyObject *a = (MyObject*)self;
    MyObject *b = (MyObject*)other;
    int result;
    
    if (!PyObject_TypeCheck(other, &MyType)) {
        Py_RETURN_NOTIMPLEMENTED;
    }
    
    switch (op) {
        case Py_EQ:
            result = (a->value == b->value);
            break;
        case Py_NE:
            result = (a->value != b->value);
            break;
        case Py_LT:
            result = (a->value < b->value);
            break;
        case Py_LE:
            result = (a->value <= b->value);
            break;
        case Py_GT:
            result = (a->value > b->value);
            break;
        case Py_GE:
            result = (a->value >= b->value);
            break;
        default:
            Py_RETURN_NOTIMPLEMENTED;
    }
    
    if (result)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

// Add to type object:
static PyTypeObject MyType = {
    // ...
    .tp_richcompare = MyObject_richcompare,
};

String Representation

static PyObject* MyObject_repr(MyObject *self) {
    return PyUnicode_FromFormat("MyObject(name=%R, value=%d)",
                                self->name, self->value);
}

static PyObject* MyObject_str(MyObject *self) {
    return PyUnicode_FromFormat("%S: %d", self->name, self->value);
}

// Add to type object:
static PyTypeObject MyType = {
    // ...
    .tp_repr = (reprfunc)MyObject_repr,
    .tp_str = (reprfunc)MyObject_str,
};

Hash and Equality

static Py_hash_t MyObject_hash(MyObject *self) {
    Py_hash_t hash = PyObject_Hash(self->name);
    if (hash == -1)
        return -1;
    // Combine with value
    hash ^= self->value;
    if (hash == -1)
        hash = -2;  // -1 is reserved for error
    return hash;
}

// Add to type object:
static PyTypeObject MyType = {
    // ...
    .tp_hash = (hashfunc)MyObject_hash,
};

Garbage Collection Support

For types containing PyObject* members that may create reference cycles:
static int MyObject_traverse(MyObject *self, visitproc visit, void *arg) {
    Py_VISIT(self->name);
    // Visit other PyObject* members
    return 0;
}

static int MyObject_clear(MyObject *self) {
    Py_CLEAR(self->name);
    // Clear other PyObject* members
    return 0;
}

static void MyObject_dealloc(MyObject *self) {
    PyObject_GC_UnTrack(self);
    MyObject_clear(self);
    Py_TYPE(self)->tp_free((PyObject*)self);
}

// Add to type object:
static PyTypeObject MyType = {
    // ...
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
    .tp_traverse = (traverseproc)MyObject_traverse,
    .tp_clear = (inquiry)MyObject_clear,
    .tp_dealloc = (destructor)MyObject_dealloc,
    .tp_alloc = PyType_GenericAlloc,
    .tp_free = PyObject_GC_Del,
};

// In __new__:
self = (MyObject*)PyType_GenericAlloc(type, 0);
// becomes:
self = (MyObject*)_PyObject_GC_New(type);

// After initialization in __init__:
PyObject_GC_Track(self);

See Also

Object Protocol

Generic object operations

Memory Allocation

Allocating type instances

Extension Modules

Creating extension modules

Reference Counting

Managing object references

Build docs developers (and LLMs) love