Fulkerson
Fulkerson

Reputation: 128

Method overriding by monkey patching

I'm trying to figure out why the following example won't work.

class BaseClass(object):
    def __init__(self):
        self.count = 1

    def __iter__(self):
        return self

    def next(self):
        if self.count:
            self.count -= 1
            return self
        else:
            raise StopIteration


class DerivedNO(BaseClass):
    pass


class DerivedO(BaseClass):
    def __init__(self):
        self.new_count = 2
        self.next = self.new_next

    def new_next(self):
        if self.new_count:
            self.new_count -= 1
            return None
        else:
            raise StopIteration


x = DerivedNO()
y = DerivedO()

print x
print list(x)
print y
print list(y)

And here is the output:

<__main__.DerivedNO object at 0x7fb2af7d1c90>
[<__main__.DerivedNO object at 0x7fb2af7d1c90>]
<__main__.DerivedO object at 0x7fb2af7d1d10>
Traceback (most recent call last):
  File "playground.py", line 41, in <module>
    print list(y)
  File "playground.py", line 11, in next
    if self.count:
AttributeError: 'DerivedO' object has no attribute 'count'

As you can see the new method will not be overridden in DerivedO when I try to assign the next() method in __init__. Why is that? A simple call to next will work fine, but not at all when using iterating techniques.

Edit: I realize my question wasn't completely clear. The AttributeError isn't the problem I'm looking to solve. But it does show that next() is called on BaseClass instead of on DerivedO as I thought it would.

Upvotes: 4

Views: 2322

Answers (2)

Martijn Pieters
Martijn Pieters

Reputation: 1121834

You cannot monkeypatch either __iter__(self) or, by extension, next(self) on instances because these methods are treated as class methods instead as a CPython internal optimization (see Special method lookup for new-style classes for an in-depth rationale as to why this is).

If you need to monkeypatch these methods, you'll need to set them directly on the class instead:

class DerivedO(BaseClass):
    def __init__(self):
        self.new_count = 2
        self.__class__.next = self.__class__.new_next

    def new_next(self):
        if self.new_count:
            self.new_count -= 1
            return None
        else:
            raise StopIteration

The above will work; note that I set __class__.next to the unbound function new_next, not to the bound method.

Upvotes: 9

unutbu
unutbu

Reputation: 879471

Since DerivedO never initializes the count attribute, an AttributeError occurs when the next method is executed.

You could avoid this error by arranging for BaseClass.__init__ to be called (either explicitly, or by using super):

class DerivedO(BaseClass):
    def __init__(self):
        super(DerivedO, self).__init__()
        self.new_count = 2

    def next(self):
        if self.new_count:
            self.new_count -= 1
            return None
        else:
            raise StopIteration

Also, instead of defining new_next, you could simply override (redefine) next.

Upvotes: -1

Related Questions