Skip to main content

Objects/classobject.c

Source:

cpython 3.14 @ ab2d84fe1023/Objects/classobject.c

Map

LinesSymbolPurpose
1–60PyMethod_NewAllocate and initialise a bound method object
61–120im_func / im_self gettersExpose __func__ and __self__ attributes
121–220method_callPrepend self to args tuple and forward to tp_call
221–300method_getattroDescriptor access; falls through to __func__ attrs
301–380method_richcompareEquality by (func, self) pair
381–460staticmethod_getReturn the wrapped function unchanged (no binding)
461–540classmethod_getReturn a bound method with the class as first argument
541–600PyClassMethod_New / PyStaticMethod_NewPublic factory functions

Reading

PyMethod_New and im_func / im_self

PyMethod_New is the single entry point for creating a bound method. It takes a callable and an instance, stores them in im_func and im_self, and returns a freshly allocated PyMethodObject. The allocator fast-paths through a free list to avoid repeated malloc pressure in tight call loops.

// CPython: Objects/classobject.c:40 PyMethod_New
PyObject *
PyMethod_New(PyObject *func, PyObject *self)
{
PyMethodObject *im;
if (self == NULL) {
PyErr_BadInternalCall();
return NULL;
}
im = free_list;
if (im != NULL) {
free_list = (PyMethodObject *)(im->im_self);
(void)PyObject_Init((PyObject *)im, &PyMethod_Type);
} else {
im = PyObject_GC_New(PyMethodObject, &PyMethod_Type);
if (im == NULL)
return NULL;
}
im->im_weakreflist = NULL;
im->im_func = Py_NewRef(func);
im->im_self = Py_NewRef(self);
PyObject_GC_Track(im);
return (PyObject *)im;
}

The im_func and im_self getters are trivial one-liners; they exist so that Python-level code can read method.__func__ and method.__self__ without going through __dict__.

method_call dispatching to tp_call

When a bound method is called, method_call builds a new argument tuple with self prepended, then forwards to the underlying function's tp_call slot. For the common case where the underlying callable is a Python function, CPython bypasses the tuple construction via _PyFunction_Vectorcall, but the general path is:

// CPython: Objects/classobject.c:153 method_call
static PyObject *
method_call(PyObject *method, PyObject *args, PyObject *kwds)
{
PyObject *self = PyMethod_GET_SELF(method);
PyObject *func = PyMethod_GET_FUNCTION(method);
PyObject *result;
Py_ssize_t nargs = PyTuple_GET_SIZE(args);

PyObject *newargs = PyTuple_New(nargs + 1);
if (newargs == NULL)
return NULL;
PyTuple_SET_ITEM(newargs, 0, Py_NewRef(self));
for (Py_ssize_t i = 0; i < nargs; i++)
PyTuple_SET_ITEM(newargs, i + 1,
Py_NewRef(PyTuple_GET_ITEM(args, i)));
result = PyObject_Call(func, newargs, kwds);
Py_DECREF(newargs);
return result;
}

method_getattro first checks the method object's own tp_dict for the attribute name; if not found, it delegates to im_func so that method.some_attr transparently returns func.some_attr.

staticmethod_get and classmethod_get

staticmethod_get is the tp_descr_get slot for staticmethod. It ignores both the instance and the owner class and returns the wrapped callable unchanged. This lets @staticmethod functions be retrieved from either an instance or the class without binding.

// CPython: Objects/classobject.c:415 staticmethod_get
static PyObject *
sm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
staticmethodobject *sm = (staticmethodobject *)self;
if (sm->sm_callable == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"uninitialized staticmethod object");
return NULL;
}
return Py_NewRef(sm->sm_callable);
}

classmethod_get binds the class (owner) rather than the instance as the first argument. It calls PyMethod_New(func, type), meaning the resulting bound method carries the class in im_self.

// CPython: Objects/classobject.c:490 classmethod_get
static PyObject *
cm_descr_get(PyObject *self, PyObject *obj, PyObject *type)
{
classmethodobject *cm = (classmethodobject *)self;
if (cm->cm_callable == NULL) {
PyErr_SetString(PyExc_RuntimeError,
"uninitialized classmethod object");
return NULL;
}
if (type == NULL)
type = (PyObject *)(Py_TYPE(obj));
return PyMethod_New(cm->cm_callable, type);
}

gopy notes

Status: not yet ported.

Planned package path: objects/ (files method.go, usertype.go).

The repo already has objects/method.go and objects/instance.go. The bound-method free list is not planned for the initial port; the allocator will use standard Go allocation. staticmethod and classmethod descriptor logic is partially covered by objects/descr.go but cm_descr_get / sm_descr_get are not yet wired to the type's TpDescrGet slot.