Skip to main content

Objects/classobject.c

Source:

cpython 3.14 @ ab2d84fe1023/Objects/classobject.c

classobject.c implements the bound method type (PyMethod) and the unbound instance method type (PyInstanceMethod). Both are produced by the descriptor protocol when a function is accessed via an instance or class.

Map

LinesSymbolRole
1-100PyMethodObject structim_func (function), im_self (instance)
101-300PyMethod_New, method_call, method_getCreation and calling
301-450method_richcomparemethod1 == method2 comparison
451-600PyInstanceMethod_Typeclassmethod-style unbound wrapper

Reading

Bound method creation

// CPython: Objects/classobject.c:22 PyMethodObject
typedef struct {
PyObject_HEAD
PyObject *im_func; /* the underlying function */
PyObject *im_self; /* the instance it's bound to */
PyObject *im_weakreflist;
} PyMethodObject;

When obj.method is accessed and method is a function defined in the class, function.__get__(obj, type) is called. That calls PyMethod_New(func, obj), binding the instance.

Calling a bound method

// CPython: Objects/classobject.c:180 method_call
static PyObject *
method_call(PyObject *method, PyObject *args, PyObject *kwargs)
{
PyMethodObject *im = (PyMethodObject *)method;
/* Prepend self to args */
PyObject *newargs = PyTuple_New(PyTuple_GET_SIZE(args) + 1);
PyTuple_SET_ITEM(newargs, 0, Py_NewRef(im->im_self));
for (Py_ssize_t i = 0; i < PyTuple_GET_SIZE(args); i++)
PyTuple_SET_ITEM(newargs, i+1, Py_NewRef(PyTuple_GET_ITEM(args, i)));
return PyObject_Call(im->im_func, newargs, kwargs);
}

The bound method prepends self to args and calls the underlying function directly.

__eq__ for methods

// CPython: Objects/classobject.c:335 method_richcompare
static PyObject *
method_richcompare(PyObject *self, PyObject *other, int op)
{
if (op != Py_EQ && op != Py_NE) Py_RETURN_NOTIMPLEMENTED;
PyMethodObject *a = (PyMethodObject *)self;
PyMethodObject *b = (PyMethodObject *)other;
int eq = PyObject_RichCompareBool(a->im_func, b->im_func, Py_EQ)
&& PyObject_RichCompareBool(a->im_self, b->im_self, Py_EQ);
...
}

Two bound methods are equal if they wrap the same function and are bound to the same (by identity) instance.

gopy notes

Bound methods are in objects/method.go. The method_call implementation is critical for performance: it must not allocate a new tuple every time. CPython uses a fast path (CALL_METHOD opcode) that avoids allocating a PyMethod object altogether for simple calls. gopy's vm/eval_call.go has a similar optimization.