Modules/_operatormodule.c
Source:
cpython 3.14 @ ab2d84fe1023/Modules/_operatormodule.c
_operatormodule.c implements the operator standard library module entirely in C. Most of
its surface area is a thin mapping from Python function calls to the CPython abstract object
API (PyNumber_*, PyObject_*, PySequence_*). The non-trivial work lives in three callable
object types: attrgetter, itemgetter, and methodcaller.
Map
| Symbol | Kind | Lines (approx) | Purpose |
|---|---|---|---|
op_add_impl through op_xor_impl | functions | 60-350 | Arithmetic, bitwise, and sequence wrappers |
operator_eq_impl through operator_le_impl | functions | 351-420 | Comparison wrappers via PyObject_RichCompareBool |
attrgetter_object | struct | 421-460 | Dotted attribute getter state |
attrgetter_new | function | 461-510 | Split dotted name on ., store parts tuple |
attrgetter_call | function | 511-560 | Chain PyObject_GetAttr over parts |
itemgetter_object | struct | 561-600 | Single-item or multi-item subscript getter |
itemgetter_call | function | 601-650 | Single vs tuple return variant |
methodcaller_new | function | 651-710 | Store name, args, kwargs |
methodcaller_call | function | 711-760 | Look up name, call with stored args |
PyMemberDef tables | data | 761-800 | Module method registration |
Reading
eq/ne/lt and the PyObject_RichCompareBool mapping
Comparison functions in _operatormodule.c are the thinnest wrappers in the file. Each one
calls PyObject_RichCompareBool with a fixed op constant and converts the C int result back
to a Python bool.
// CPython: Modules/_operatormodule.c:356 operator_eq_impl
static PyObject *
operator_eq_impl(PyObject *module, PyObject *a, PyObject *b)
{
return PyBool_FromLong(PyObject_RichCompareBool(a, b, Py_EQ));
}
ne, lt, le, gt, and ge follow the same pattern, each passing the corresponding
Py_NE, Py_LT, etc. constant. PyObject_RichCompareBool handles None comparisons and
calls __eq__ (or the appropriate slot) on the left operand with a fallback to the right.
attrgetter_new: dotted path splitting
When the name string contains a ., attrgetter_new splits it and stores the parts as a
Python tuple of interned strings. A plain name is stored as a single string object, not a
tuple, so the common case remains allocation-free at call time.
// CPython: Modules/_operatormodule.c:468 attrgetter_new
static PyObject *
attrgetter_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
...
if (PyUnicode_Find(attr, dot, 0, PyUnicode_GET_LENGTH(attr), 1) < 0) {
/* No dot: store the name directly */
ag->attr = Py_NewRef(attr);
ag->nattrs = 1;
} else {
ag->attr = PyUnicode_Split(attr, dot, -1);
ag->nattrs = PyList_GET_SIZE(ag->attr);
}
...
}
attrgetter_call: chained PyObject_GetAttr
At call time the getter walks the stored parts tuple, feeding the output of one GetAttr as
the input to the next. An error on any segment short-circuits the loop and propagates the
AttributeError unchanged.
// CPython: Modules/_operatormodule.c:524 attrgetter_call
static PyObject *
attrgetter_call(attrgetterobject *ag, PyObject *args, PyObject *kw)
{
PyObject *obj = PyTuple_GET_ITEM(args, 0);
Py_INCREF(obj);
for (Py_ssize_t i = 0; i < ag->nattrs; i++) {
PyObject *attr = PyTuple_GET_ITEM(ag->attr, i);
PyObject *newobj = PyObject_GetAttr(obj, attr);
Py_DECREF(obj);
if (newobj == NULL) return NULL;
obj = newobj;
}
return obj;
}
itemgetter_call: single vs tuple variant
itemgetter dispatches on nitems at construction time to choose between two call paths.
The single-item path calls PyObject_GetItem and returns the result directly. The multi-item
path allocates a fresh tuple and fills it with one GetItem call per stored key.
// CPython: Modules/_operatormodule.c:614 itemgetter_call
static PyObject *
itemgetter_call(itemgetterobject *ig, PyObject *args, PyObject *kw)
{
PyObject *obj = PyTuple_GET_ITEM(args, 0);
if (ig->nitems == 1) {
return PyObject_GetItem(obj, ig->item);
}
PyObject *retval = PyTuple_New(ig->nitems);
if (retval == NULL) return NULL;
for (Py_ssize_t i = 0; i < ig->nitems; i++) {
PyObject *item = PyObject_GetItem(obj, PyTuple_GET_ITEM(ig->items, i));
if (item == NULL) { Py_DECREF(retval); return NULL; }
PyTuple_SET_ITEM(retval, i, item);
}
return retval;
}
methodcaller_new and methodcaller_call
methodcaller_new interns the method name string and stores the positional arguments as a
tuple and keyword arguments as a dict. methodcaller_call resolves the attribute on the
passed object and calls it with the stored arguments using PyObject_Call.
// CPython: Modules/_operatormodule.c:720 methodcaller_call
static PyObject *
methodcaller_call(methodcallerobject *mc, PyObject *args, PyObject *kw)
{
PyObject *obj = PyTuple_GET_ITEM(args, 0);
PyObject *method = PyObject_GetAttr(obj, mc->name);
if (method == NULL) return NULL;
PyObject *result = PyObject_Call(method, mc->args, mc->kwds);
Py_DECREF(method);
return result;
}
gopy notes
Status: not yet ported.
Planned package path: module/operator/. The arithmetic and comparison wrappers are
mechanical and can be generated from the PyMemberDef table. attrgetter dotted-name
splitting will use strings.SplitN. operator.contains calls PySequence_Contains, which
returns -1 on error, 0 for absent, 1 for present; the Go port maps this to (bool, error).
methodcaller.__reduce__ must be ported to pass pickle round-trip tests.