0

Suppose I define a custom list-type:

class MyList(list):
    def __reversed__(self):
        print 'called MyList.__reversed__'
        return super(MyList, self).__reversed__()

    def count(self, item):
        print 'called MyList.count'
        return super(MyList, self).count(item)

All this does is indicate when either __reversed__ or count is called.

Now suppose I define another type with a MyList member variable. I'd like a couple of the methods on my new type to dispatch to the MyList methods so I set the methods in __init__ like so:

class GoodList:
    def __init__(self, iterable):
        self._list = MyList(iterable)
        self.__reversed__ = self._list.__reversed__
        self.count = self._list.count

    def __getitem__(self, index):
        return 0

    def __len__(self):
        return 0

Later I decide that GoodList should inherit from collections.Sequence. But when I do so, the behavior changes. As an example, I'll define BadList, the only difference being the inheritance:

from collections import Sequence

class BadList(Sequence):
    def __init__(self, iterable):
        self._list = MyList(iterable)
        self.__reversed__ = self._list.__reversed__
        self.count = self._list.count

    def __getitem__(self, index):
        return 0

    def __len__(self):
        return 0

Now when I create a GoodList and call __reversed__ and count, I get the messages I expect, printed from MyList. Here's the code to exercise it:

print 'GOOD ' + '*' * 75
good = GoodList([1, 2, 3, 4, 5])
print 'GOOD list(reversed(good))'
print list(reversed(good))
print 'GOOD good.count(3)'
print good.count(3)

And here's the output:

GOOD ***************************************************************************
GOOD list(reversed(good))
called MyList.__reversed__
[5, 4, 3, 2, 1]
GOOD good.count(3)
called MyList.count
1

Instead, when I create a BadList and call __reversed__ and count, I get the message I expect only for count. The __reversed__ method is dispatched to the inherited Sequence.__reversed__ method. Here's the code to exercise it:

print 'BAD ' + '*' * 76
bad = BadList([1, 2, 3, 4, 5])
print 'BAD list(reversed(bad))'
print list(reversed(bad))
print 'BAD bad.count(3)'
print bad.count(3)

And here's the output:

BAD ****************************************************************************
BAD list(reversed(bad))
[]
BAD bad.count(3)
called MyList.count
1

Why does method dispatch appear to work differently for double-under (dunder) methods? How can I inherit from Sequence but still make __reversed__ dispatch to _list.__reversed__?

GrantJ
  • 8,162
  • 3
  • 52
  • 46
  • 1
    Magic methods are always looked up on the class, not the instance. See [this previous question](http://stackoverflow.com/questions/9057669/how-can-i-intercept-calls-to-pythons-magic-methods-in-new-style-classes) for an approach to reducing boilerplate when you want to define a lot of magic methods. For your case, though, since you only need to do this for a few, you could just write the methods yourself. The thing is that you have to actually write a method `__reversed__` in the class that calls `self._list.__reversed__`; you can't assign `__reversed__` on the instance. – BrenBarn Nov 29 '14 at 00:52
  • Ok, two questions then: (1) Why is it done that way? (2) There's really no possible way? (Mostly curious. What about using e.g. `__new__`?) – GrantJ Nov 29 '14 at 01:15
  • 1
    The rationale is described [here](https://docs.python.org/2/reference/datamodel.html#special-method-lookup-for-new-style-classes). Basically it is because classes are also instances (of type), so if things were looked up the instance, magic methods wouldn't work on class objects. As for your question 2, I'm not sure what you mean. There's no way to make magic method lookup work on instances, but you can get the behavior you want for your question by doing what I suggested, or something like what's shown in the answer to the other question I linked to. – BrenBarn Nov 29 '14 at 01:23

0 Answers0