This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: __rdiv__ vs new-style classes
Type: Stage:
Components: Interpreter Core Versions: Python 2.2
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: gvanrossum Nosy List: gvanrossum, tim.peters
Priority: high Keywords:

Created on 2002-10-15 18:00 by tim.peters, last changed 2022-04-10 16:05 by admin. This issue is now closed.

Messages (3)
msg12796 - (view) Author: Tim Peters (tim.peters) * (Python committer) Date: 2002-10-15 18:00
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.
msg12797 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2003-01-06 22:02
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...
msg12798 - (view) Author: Guido van Rossum (gvanrossum) * (Python committer) Date: 2003-01-06 23:00
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.
History
Date User Action Args
2022-04-10 16:05:45adminsetgithub: 37325
2002-10-15 18:00:04tim.peterscreate