[Python-ideas] A macro for easier rich comparisons
Petr Viktorin
encukou at gmail.com
Wed Mar 18 11:53:52 CET 2015
Hello,
Rich comparison functions of many builtin types include a block of
boilerplate code with a switch on the "op" argument, like this example
from tupleobject:
int cmp;
PyObject *res;
switch (op) {
case Py_LT: cmp = vlen < wlen; break;
case Py_LE: cmp = vlen <= wlen; break;
case Py_EQ: cmp = vlen == wlen; break;
case Py_NE: cmp = vlen != wlen; break;
case Py_GT: cmp = vlen > wlen; break;
case Py_GE: cmp = vlen >= wlen; break;
default: return NULL; /* cannot happen */
}
if (cmp)
res = Py_True;
else
res = Py_False;
Py_INCREF(res);
return res;
Each time it's implemented slightly differently: sometimes there are two
values to compare, sometimes there's a cmp-style 0/1/-1 result to
process; invalid "op" values are handled in various ways.
Such code is also required in third-party extensions.
I propose adding a public macro to ease this. My version takes two
C-orderable values and the operation, similar to richcmpfunc:
#define Py_RICHCOMPARE(val1, val2, op) ( \
((op) == Py_EQ) ? PyBool_FromLong((val1) == (val2)) : \
((op) == Py_NE) ? PyBool_FromLong((val1) != (val2)) : \
((op) == Py_LT) ? PyBool_FromLong((val1) < (val2)) : \
((op) == Py_GT) ? PyBool_FromLong((val1) > (val2)) : \
((op) == Py_LE) ? PyBool_FromLong((val1) <= (val2)) : \
((op) == Py_GE) ? PyBool_FromLong((val1) >= (val2)) : \
(Py_INCREF(Py_NotImplemented), Py_NotImplemented))
(As for the behavior for unknown op: for most cases the best thing to do
is setting an error and returning NULL, but that doesn't fit into a
macro. A surprising number of richcmpfunc's in CPython either return
NULL without error (e.g. tupleobject), or fall through and return
True/False arbitrarily (as in bytearrayobject). Datetime does an assert.
I think Py_NotImplemented is a good value to return)
I've made a patch with the macro and its use in CPython itself.
Read it as examples of usage: I'm not saying that all these modules
should start using it immediately (or ever, e.g. for speed reasons), and
if they do, many could use some refactoring in other code paths.
The patch is here:
https://github.com/encukou/cpython/compare/master...richcmp
Why I'm interested in this: When porting old code to Python 3, rich
comparisons a major cause of added boilerplate – especially when
wrapping a C library that exposes either trivially comparable types or
"cmp" functions. With this macro, a typical trivial rich comparison
function reduces to:
static PyObject* mytype_richcmp(PyObject *obj1, PyObject *obj2, int op)
{
if (mytype_Check(obj1) && mytype_Check(obj2)) {
return PY_RICHCOMPARE(mytype_get_data(obj1),
mytype_get_data(obj2), op);
}
Py_RETURN_NOTIMPLEMENTED;
}
... or with `PY_RICHCOMPARE(mytype_cmp(obj1, obj2), 0, op)`.
(Note that even adding this to 3.5/3.6, would help in porting efforts:
the macro itself is be easy to backport, but there are big benefits in
having a standard name, known semantics, and official documentation.)
Is this a PEP-worthy idea?
More information about the Python-ideas
mailing list