[Python-bugs-list] [ python-Bugs-623669 ] __rdiv__ vs new-style classes

SourceForge.net noreply@sourceforge.net
Mon, 06 Jan 2003 15:00:34 -0800


Bugs item #623669, was opened at 2002-10-15 14:00
You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=105470&aid=623669&group_id=5470

Category: Type/class unification
Group: Python 2.2.2
>Status: Closed
>Resolution: Fixed
Priority: 7
Submitted By: Tim Peters (tim_one)
Assigned to: Guido van Rossum (gvanrossum)
Summary: __rdiv__ vs new-style classes

Initial Comment:
In 2.2.2 and 2.3, consider this:

"""
class F:
    def __init__(self):
        print 'built an F'
    def __div__(self, other):
        print 'in F.__div__'
        return F()
    def __rdiv__(self, other):
        print 'in F.__rdiv__'
        return F() / self

class F2(F):
    def __init__(self):
        print 'built an F2'

3 / F2()
"""

This displays what I expect:

"""
built an F2
in F.__rdiv__
built an F
in F.__div__
built an F
"""

However, change F to derive from object:

class F(object):

and it's in infinite recursion, starting like so:

"""
built an F2
in F.__rdiv__
built an F
in F.__rdiv__
built an F
in F.__rdiv__
built an F
in F.__rdiv__
built an F
in F.__rdiv__
built an F
in F.__rdiv__
built an F
in F.__rdiv__
...
"""

Despite that F.__rdiv__ creates an explicit F() instance 
and uses it on the LHS of /, F.__div__ is never invoked; 
instead the RHS F2.__rdiv__ == F.__rdiv__ always gets 
the nod.

Maybe this is intentional?  I confess I've lost track of the 
rules.

Changing the end of F.__rdiv__ to

return F().__div__(self)

brute-forces the desired outcome, so there is a 
workaround.

----------------------------------------------------------------------

>Comment By: Guido van Rossum (gvanrossum)
Date: 2003-01-06 18:00

Message:
Logged In: YES 
user_id=6380

OK, I've checked in a fix that checks whether other actually
overrides the __rdiv__ method (rather than ingeriting it
from self.__class__).

Sigh, this was hard work.

----------------------------------------------------------------------

Comment By: Guido van Rossum (gvanrossum)
Date: 2003-01-06 17:02

Message:
Logged In: YES 
user_id=6380

I've got a dilemma.

The cause of the recursion is in line 3509 of typeobject.c,
in the SLOT1BINFULL() macro.  The section starting with

  if (do_other && \

(introduced by rev 2.82 of thatfile) tries to call the right
operand's __rdiv__ *before* the left operand's __div__ is
called in the specific case where both operands are
new-style classes that implement __div__ and __rdiv__.

This is intended to "do the right thing" in cases where D
derives from C and overrides some operation, and you call
C()/D(); without that code section, C.__div__ would be
invoked before D.__rdiv__, and since D() is a C instance,
C.__div__ would probably return a result -- just not the
result that D.__rdiv__ would have given.

The endless recursion reported above is caused by the fact
that D satisfies all the criteria, but D.__rdiv__ is really
just C.__rdiv__, which calls C()/D() recursively.

My dilemma is that this feature is not documented, and
classic classes don't work this way (C.__div__ would be
called). There are also no unit tests for the feature. So
ripping it out would be the quickest way to avoid the
recursion. OTOH, binary_op1() in abstract.c contains similar
code that catches a similar case where the base class C is a
built-in type (e.g. int). (Also undocumented and also
without unit tests.)

But checking that D's __rdiv__ is really the same as C's
__rdiv__ before calling it is fairly expensive and
convoluted (imagine the case where there's an intermediate
class C1, so that D derives from C1 derives from C, and C1
overrides C.__rdiv__). And even then one could construct
cases that would fool the test.

I'm going to think about this some more...

----------------------------------------------------------------------

You can respond by visiting: 
https://sourceforge.net/tracker/?func=detail&atid=105470&aid=623669&group_id=5470