DangerMouse DangerMouse - 1 month ago 6
Python Question

Is it possible to overwrite str's % behaviour using __rmod__?

I'd like to do:

x %doSomething% y


which is easy enough to do (see code below) for any x and any y except in the case that x is a str.

Is there any way (e.g. adding a special method or raising a specific error) to cause old style string formatting to fail (similarly to how 1 %doSomthing fails with a TypeError) and revert to the __rmod__ method defined in the doSomething object?

class BinaryMessage(object):
def __init__(self, fn):
self._fn = fn
def __rmod__(self, LHS):
return BinaryMessagePartial(self._fn, LHS)

class BinaryMessagePartial(object):
def __init__(self, fn, LHS):
self._fn = fn
self._LHS = LHS
def __mod__(self, RHS):
return self._fn(self._LHS, RHS)

def _doSomething(a , b):
return a + b

doSomething = BinaryMessage(_doSomething)

result = 5 %doSomething% 6
assert result == 11

Answer

Unfortunately, this is not currently possible in Python. The behaviour is hardcoded in the evaluation loop:

TARGET(BINARY_MODULO) {
    PyObject *divisor = POP();
    PyObject *dividend = TOP();
    PyObject *res = PyUnicode_CheckExact(dividend) ?
        PyUnicode_Format(dividend, divisor) :
        PyNumber_Remainder(dividend, divisor);

(From the Python 3.5 source code, where PyUnicode is the Python str type).

This is unfortunate, because for every other type you can prevent the LHS.__mod__ method to be invoked by using a subclass for the right-hand operand; from the documentation:

Note: If the right operand’s type is a subclass of the left operand’s type and that subclass provides the reflected method for the operation, this method will be called before the left operand’s non-reflected method. This behavior allows subclasses to override their ancestors’ operations.

This would have been the only option here, str % other never returns NotImplemented, all RHS types are accepted (the actual str.__mod__ method only accepts str objects for the RHS, but is not called in this case).

I consider this a bug in Python, filed as issue #28598.

Comments