Consider this simplified situation:
class Decoder:
def __str__(self):
return self.__bytes__().decode('ascii')
class Comment(Decoder, bytes):
def __bytes__(self):
return b'#' + self
Usage:
Comment(b'foo')
Prints:
b'foo'
Instead of expected:
#foo
Regardless of the order in Comment.mro()
(i.e. I can swap Decoder
and bytes
in the supeclass list), Decoder.__str__()
is never called.
What gives?
Comment(b'foo')
calls Comment.__new__
, which, not being defined, resolves to either Decoder.__new__
or bytes.__new__
, depending on the order in which you list them in the definition of Comment
.
The MRO for Comment
is Comment
, bytes
, Decoder
, object
. However, the functions actually being called are:
Comment.__new__
, to create a new object. Since that function isn't defined, we next call bytes.__new__
, which is defined. It effectively just calls object.__new__(Comment, b'foo')
, giving you your final object.
To display the return value of Comment
, the interpreter tries to call Comment.__repr__
, not Comment.__str__
. Again, the function isn't defined, so it falls back to bytes.__repr__
, giving the observed result.