From 4bb1e36eec19b30f2e582fceffa250e1598262e1 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Fri, 28 Sep 2001 23:49:48 +0000 Subject: It's a fact: for binary operators, *under certain circumstances*, __rop__ now takes precendence over __op__. Those circumstances are: - Both arguments are new-style classes - Both arguments are new-style numbers - Their implementation slots for tp_op differ - Their types differ - The right argument's type is a subtype of the left argument's type Also did this for the ternary operator (pow) -- only the binary case is dealt with properly though, since __rpow__ is not supported anyway. --- Lib/test/test_descr.py | 37 +++++++++++++++++++++++- Objects/abstract.c | 76 +++++++++++++++++++++++++++++++------------------- 2 files changed, 84 insertions(+), 29 deletions(-) diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 2c5e7a4..22ae09a 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -1,8 +1,12 @@ -# Test descriptor-related enhancements +# Test enhancements related to descriptors and new-style classes from test_support import verify, verbose, TestFailed, TESTFN from copy import deepcopy +def vereq(a, b): + if a != b: + raise TestFailed, "%r != %r" % (a, b) + def testunop(a, res, expr="len(a)", meth="__len__"): if verbose: print "checking", expr dict = {'a': a} @@ -2133,6 +2137,36 @@ def copies(): a.bar.append(4) verify(d.bar == [1,2,3]) +def binopoverride(): + if verbose: print "Testing overrides of binary operations..." + class I(int): + def __repr__(self): + return "I(%r)" % int(self) + def __add__(self, other): + return I(int(self) + int(other)) + __radd__ = __add__ + def __pow__(self, other, mod=None): + if mod is None: + return I(pow(int(self), int(other))) + else: + return I(pow(int(self), int(other), int(mod))) + def __rpow__(self, other, mod=None): + if mod is None: + return I(pow(int(other), int(self), mod)) + else: + return I(pow(int(other), int(self), int(mod))) + + vereq(`I(1) + I(2)`, "I(3)") + vereq(`I(1) + 2`, "I(3)") + vereq(`1 + I(2)`, "I(3)") + vereq(`I(2) ** I(3)`, "I(8)") + vereq(`2 ** I(3)`, "I(8)") + vereq(`I(2) ** 3`, "I(8)") + vereq(`pow(I(2), I(3), I(5))`, "I(3)") + class S(str): + def __eq__(self, other): + return self.lower() == other.lower() + def test_main(): lists() @@ -2178,6 +2212,7 @@ def test_main(): setclass() pickles() copies() + binopoverride() if verbose: print "All OK" if __name__ == "__main__": diff --git a/Objects/abstract.c b/Objects/abstract.c index fdf1a44..c353159 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -300,11 +300,14 @@ PyNumber_Check(PyObject *o) v w Action ------------------------------------------------------------------- - new new v.op(v,w), w.op(v,w) + new new w.op(v,w)[*], v.op(v,w), w.op(v,w) new old v.op(v,w), coerce(v,w), v.op(v,w) old new w.op(v,w), coerce(v,w), v.op(v,w) old old coerce(v,w), v.op(v,w) + [*] only when v->ob_type != w->ob_type && w->ob_type is a subclass of + v->ob_type + Legend: ------- * new == new style number @@ -318,29 +321,35 @@ static PyObject * binary_op1(PyObject *v, PyObject *w, const int op_slot) { PyObject *x; - binaryfunc *slot; - if (v->ob_type->tp_as_number != NULL && NEW_STYLE_NUMBER(v)) { - slot = NB_BINOP(v->ob_type->tp_as_number, op_slot); - if (*slot) { - x = (*slot)(v, w); - if (x != Py_NotImplemented) { - return x; - } - Py_DECREF(x); /* can't do it */ - } - if (v->ob_type == w->ob_type) { - goto binop_error; - } + binaryfunc slotv = NULL; + binaryfunc slotw = NULL; + + if (v->ob_type->tp_as_number != NULL && NEW_STYLE_NUMBER(v)) + slotv = *NB_BINOP(v->ob_type->tp_as_number, op_slot); + if (w->ob_type != v->ob_type && + w->ob_type->tp_as_number != NULL && NEW_STYLE_NUMBER(w)) { + slotw = *NB_BINOP(w->ob_type->tp_as_number, op_slot); + if (slotw == slotv) + slotw = NULL; + } + if (slotw && PyType_IsSubtype(w->ob_type, v->ob_type)) { + x = slotw(v, w); + if (x != Py_NotImplemented) + return x; + Py_DECREF(x); /* can't do it */ + slotw = NULL; } - if (w->ob_type->tp_as_number != NULL && NEW_STYLE_NUMBER(w)) { - slot = NB_BINOP(w->ob_type->tp_as_number, op_slot); - if (*slot) { - x = (*slot)(v, w); - if (x != Py_NotImplemented) { - return x; - } - Py_DECREF(x); /* can't do it */ - } + if (slotv) { + x = slotv(v, w); + if (x != Py_NotImplemented) + return x; + Py_DECREF(x); /* can't do it */ + } + if (slotw) { + x = slotw(v, w); + if (x != Py_NotImplemented) + return x; + Py_DECREF(x); /* can't do it */ } if (!NEW_STYLE_NUMBER(v) || !NEW_STYLE_NUMBER(w)) { int err = PyNumber_CoerceEx(&v, &w); @@ -350,9 +359,10 @@ binary_op1(PyObject *v, PyObject *w, const int op_slot) if (err == 0) { PyNumberMethods *mv = v->ob_type->tp_as_number; if (mv) { - slot = NB_BINOP(mv, op_slot); - if (*slot) { - PyObject *x = (*slot)(v, w); + binaryfunc slot; + slot = *NB_BINOP(mv, op_slot); + if (slot) { + PyObject *x = slot(v, w); Py_DECREF(v); Py_DECREF(w); return x; @@ -363,7 +373,6 @@ binary_op1(PyObject *v, PyObject *w, const int op_slot) Py_DECREF(w); } } -binop_error: Py_INCREF(Py_NotImplemented); return Py_NotImplemented; } @@ -420,6 +429,18 @@ ternary_op(PyObject *v, register ternaryfunc *slot; mv = v->ob_type->tp_as_number; + mw = w->ob_type->tp_as_number; + if (v->ob_type != w->ob_type && mw && NEW_STYLE_NUMBER(w)) { + slot = NB_TERNOP(mw, op_slot); + if (*slot && *slot != *NB_TERNOP(mv, op_slot) && + PyType_IsSubtype(w->ob_type, v->ob_type)) { + x = (*slot)(v, w, z); + if (x != Py_NotImplemented) + return x; + /* Can't do it... fall through */ + Py_DECREF(x); + } + } if (mv != NULL && NEW_STYLE_NUMBER(v)) { /* try v.op(v,w,z) */ slot = NB_TERNOP(mv, op_slot); @@ -435,7 +456,6 @@ ternary_op(PyObject *v, goto ternary_error; } } - mw = w->ob_type->tp_as_number; if (mw != NULL && NEW_STYLE_NUMBER(w)) { /* try w.op(v,w,z) */ slot = NB_TERNOP(mw,op_slot); -- cgit v0.12