[Python-ideas] divmod(): fallback to __floordiv__ and __mod__?

Steven D'Aprano steve at pearwood.info
Sun Sep 18 22:04:12 EDT 2016


On Sat, Sep 17, 2016 at 09:01:53AM +0000, Spencer Brown wrote:

> Currently, calling divmod() on a class with __floordiv__ and __mod__ 
> defined, but not __divmod__ raises a TypeError. Is there any reason 
> why it doesn't fallback to (self // x, self % x)? 

Because things get really complex, fast. Does the added complexity pay 
its own way?

I may have some of the details wrong, but I understand the 
implementation of divmod() is something like this:

# pseudo-code for divmod
def divmod(a, b):
    A, B = type(a), type(b)
    if issubclass(B, A):
        # Give priority to the reverse dunder version
        if hasattr(B, '__rdivmod__'):
            result = B.__rdivmod__(b, a)
            if result is not NotImplemented:
                return result
        if hasattr(A, '__divmod__'):
            result = A.__divmod__(a, b)
            if result is not NotImplemented:
                return result
        raise TypeError
    if hasattr(A, '__divmod__'):
        result = A.__divmod__(a, b)
        if result is not NotImplemented:
            return result
    if hasattr(B, '__rdivmod__'):
        result = B.__rdivmod__(b, a)
        if result is not NotImplemented:
            return result
    raise TypeError
    

only it will be in C, so probably three or five times as much code :-)

Now consider adding a fallback to __floordiv__ and __mod__ as suggested. 
That adds a lot more complexity:

- what if the object has __floordiv__, but not __mod__? Do you try 
  the reversed method, __rmod__, instead?

- likewise for NotImplemented?

- what if they're None?


So let's look at the complexity of the fallback:


# reverse dunder version, for when b is a subclass of a
rdiv = getattr(B, '__rfloordiv__, None)
rmod = getattr(B, '__rmod__, None)
if rdiv is not None and rmod is not None:
    x, y = rdiv(b, a), rmod(b, a)
    if x is NotImplemented:
        div = getattr(A, '__floordiv__', None)
        if div is not None:
            x = div(a, b)
            if x is NotImplemented:
                raise TypeError
    if y is NotImplemented:
        mod = getattr(A, '__mod__', None)
        if mod is not None:
            y = mod(a, b)
            if y is NotImplemented:
                raise TypeError
    assert NotImplemented not in (x, y)
else:
    # try the __floordiv__ and __mod__ versions, without falling 
    # back to reversed versions
    ...
return (x, y)


And then more or less the same for the "standard" case where a has 
priority over b (i.e. isn't a subclass). So, my estimate is that this 
will roughly triple the complexity of the divmod() function. Perhaps 
worse.

Oh, and I forgot the case where __divmod__ exists but is None, but I'm 
not going back to add it in because I'm not even sure where that ought 
to be tested.

Now I daresay we can refactor my naive pseudo-code above, but however 
you look at it, there's going to be a big jump in complexity for not a 
lot of benefit. In the case of __ne__ falling back on not __eq__, there 
is at least an utterly clear and obvious user-expectation that the two 
methods are linked, and there are no reversed __req__ and __rne__ 
methods to care about. That's not the case here.

Going the other way (fall back to __divmod__ if __floordiv__ or __mod__ 
is not defined) will probably be not quite as complex, but it will still 
add a fair amount of complexity.

So, *emotionally* I actually do quite like the idea of having divmod 
fall back to // and %, *and* the other way, but *intellectually* when I 
think about the complexity and the tests that will be needed to cover 
all the cases, I have to say "Uh uh, no way!"

It may be that the actual C implementation is simpler than I think, in 
which case maybe this is a reasonable idea, but on the face of it, I 
think that it will be really hard to get right, add a lot of code, and 
provide very little benefit.



-- 
Steve


More information about the Python-ideas mailing list