Skip to main content

classobject.c: PyInstanceMethod and PyMethod

Objects/classobject.c defines two distinct object types that wrap callables with a receiver. PyInstanceMethod wraps an unbound function stored on a class (exposed via im_func). PyMethod (the bound method) pairs a function with a self instance (im_func + im_self) and is what you get when you look up a function through an instance.

Map

LinesSymbolPurpose
1-40PyInstanceMethod_NewAllocate and populate im_func for unbound wrappers
41-90instancemethod_get / instancemethod_setDescriptor protocol on im_func
91-180PyMethod_NewAllocate PyMethodObject, store im_func and im_self
181-260method_vectorcallFast-path call that prepends self into the arg vector
261-340method_callFallback tp_call wrapping method_vectorcall
341-420method_richcompare== compares im_func and im_self; hash combines both
421-500method_reprHuman-readable <bound method X.f of Y>
501-600Type objectsPyInstanceMethod_Type and PyMethod_Type slot tables

Reading

method_vectorcall

The vectorcall protocol lets the VM call a bound method without building a temporary tuple. method_vectorcall shifts the argument stack pointer back by one slot, writes self there, and forwards the widened vector to the wrapped function.

// Objects/classobject.c:190 method_vectorcall
static PyObject *
method_vectorcall(PyObject *method, PyObject *const *args,
size_t nargsf, PyObject *kwnames)
{
PyObject *self = ((PyMethodObject *)method)->im_self;
PyObject *func = ((PyMethodObject *)method)->im_func;
Py_ssize_t nargs = PyVectorcall_NARGS(nargsf);
PyObject **newargs = (PyObject **)args - 1;
newargs[0] = self;
return _PyObject_Vectorcall(func, newargs, nargs + 1, kwnames);
}

The args - 1 trick is valid because CPython reserves one extra slot below every call-site argument frame precisely for this prepend pattern.

method_richcompare

Two bound methods are equal when both their function and their self are equal. The hash is derived from XOR(hash(im_func), hash(im_self)) so that equal methods hash identically without a dedicated hash table.

// Objects/classobject.c:350 method_richcompare
if (op == Py_EQ || op == Py_NE) {
eq = PyObject_RichCompareBool(a->im_func, b->im_func, Py_EQ);
if (eq == 1)
eq = PyObject_RichCompareBool(a->im_self, b->im_self, Py_EQ);
...
}

3.14 changes

Python 3.14 added a __firstlineno__ slot to method objects and aligned the internal layout with the tier-2 optimizer's type version tags. The im_weakreflist field moved to reduce padding on 64-bit platforms.

gopy notes

The port lives in objects/method.go. PyMethod_New maps to NewBoundMethod, which stores the function and receiver in a BoundMethodObject struct. The vectorcall optimization is approximated in vm/eval_call.go by checking for BoundMethodObject before the generic call path and prepending the receiver to the argument slice. method_richcompare is implemented as the Eq method on BoundMethodObject using ObjectRichCompareBool from objects/protocol.go.

PyInstanceMethod (the unbound wrapper) maps to objects/instance.go InstanceMethodObject. It is only created during class body execution and is not heap-allocated per lookup.