pythonでnextメソッドを動的に置き換えるには

~~pythonのfor文の挙動について勘違いしているかもしれないので、理由に心当たりがありましたら教えていただけるとうれしいです。~~解決しました。

# 準備

同じ文字列を3回返すイテレータ

```py:iter.py
# coding: utf-8

class Iterator(object):
    def __init__(self):
        self.counter = 3

    def __iter__(self):
        return self

    def next(self):
        self.counter -= 1
        if self.counter < 0:
            raise StopIteration
        return "わーい！"
```
確認

```
>>> from iter import Iterator
>>> instance = Iterator()
>>> iterator = iter(instance)
>>> print iterator.next()
わーい！
>>> print iterator.next()
わーい！
>>> print iterator.next()
わーい！
>>> print iterator.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "iter.py", line 13, in next
    raise StopIteration
StopIteration
```
for文もいける

```
>>> for i in Iterator():
...     print i
...
わーい！
わーい！
わーい！
```

#本題
`Iterator`を継承し、初期化時に元のイテレーションを1回だけ進めた後、`next`メソッドを置き換える`RunTimeIterator`

```py:iter.py
# coding: utf-8
class Iterator(object):
    def __init__(self):
        self.counter = 3

    def __iter__(self):
        return self

    def next(self):
        self.counter -= 1
        if self.counter < 0:
            raise StopIteration
        return "わーい！"


class RunTimeIterator(Iterator):
    def __init__(self):
        super(RunTimeIterator, self).__init__()

        for i in self:
            print i
            break
        self.next = self.alter_next

    def alter_next(self):
        self.counter -= 1
        if self.counter < 0:
            raise StopIteration
        return "たーのしー！"
```
確認

```
>>> from iter import RunTimeIterator
>>> instance = RunTimeIterator()
わーい！
>>> iterator = iter(instance)
>>> print iterator.next()
たーのしー！
>>> print iterator.next()
たーのしー！
>>> print iterator.next()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "iter.py", line 28, in alter_next
    raise StopIteration
StopIteration
```
さてこれをfor文で動かすと、

```
>>> for i in RunTimeIterator():
...     print i
...
わーい！
わーい！
わーい！
```

なぜかうまくいかない。

# なんで？

~~わかんないや。
`next`メソッドを直接置き換えようとすると起きる問題らしく、上記の例なら返す文字列をインスタンス変数に保持して置き換えれば普通に動くし、より一般的には`next`メソッドが別のメソッドの実装を返すようにし、そちらを置き換えるようにするとうまくいく。~~

実は`next`メソッドを直接呼ばず、build-inの`next()`関数を使うとfor文と同じ現象が起きる。

```
>>> from iter import RunTimeIterator
>>> instance = RunTimeIterator()
わーい！
>>> iterator = iter(instance)
>>> print next(iterator)
わーい！
>>> print next(iterator)
わーい！
>>> print next(iterator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "iter.py", line 12, in next
    raise StopIteration
StopIteration
```

for文の挙動としては、イテレータオブジェクトの`next`メソッドを直接呼ばずに、`next()`関数を用いてイテレーションを行っているようだ。
そもそも`next()`関数ができた理由が、`__iter__`や`__len__`を直接呼ばず`iter()`や`len()`を使うように、`next`メソッドを直接呼ばずに済むようにする（加えてpython3での`__next__`への改名の都合がある？）ためなようなので[^1]、インスタンスの`next`メソッドを直接呼ぶのはあまり推奨されないようだ。

結局の所、問題なのはbuild-inの`next()`が返すのが、イテレーションを行う時点でのインスタンスの`next`メソッドではなく、クラス定義時の`next`メソッドになっている点なのだが、どうやらイテレータの実装はクラス定義時にセットされるのが原因らしい[^2]。

インスタンスの`next`メソッドを置き換えても実際のイテレータには反映されず、クラスの`next`メソッドを直接書き換えると動くには動く。

```py:
class RunTimeIterator(Iterator):
    def __init__(self):
        super(RunTimeIterator, self).__init__()

        for i in self:
            print i
            break
        # self.next = self.alter_next
        self.__class__.next = self.alter_next

    def alter_next(self):
        self.counter -= 1
        if self.counter < 0:
            raise StopIteration
        return "たーのしー！"
```
とはいえクラスレベルで置き換えてしまうと、2つ以上インスタンスを作った時にはもちろんこうなる。

```
>>> from iter import RunTimeIterator
>>> for i in RunTimeIterator():
...     print i
...
わーい！
たーのしー！
たーのしー！
>>> for i in RunTimeIterator():
...     print i
...
たーのしー！
たーのしー！
たーのしー！
```

#解法？
`next`メソッドは直接置き換えられないので、`next`メソッドが別のメソッドの実装を返すようにして、置き換えたい時にはそちらを換えるようにする。
次の例では、`next`メソッドは`_next`メソッドの結果を返すようにし、`next`メソッドを置き換えたい時には`_next`メソッドを置き換えている。

```py
class RunTimeIterator(Iterator):
    def __init__(self):
        super(RunTimeIterator, self).__init__()

        for i in self:
            print i
            break
        self._next = self.alter_next

    def next(self):
        """ 本物の`next`メソッドだけど中身は`_next`
        """
        return self._next()

    def _next(self):
        """ 実質的な`next`メソッド

        初期状態として親の`next`メソッドを使うようにする。
        `_next`メソッドを定義する代わりに、`__init__`に

        self._next = super(RunTimeIterator, self).next

        と書いても同じ。
        """
        return super(RunTimeIterator, self).next()

    def alter_next(self):
        self.counter -= 1
        if self.counter < 0:
            raise StopIteration
        return "たーのしー！"
```
```
>>> from iter import RunTimeIterator
>>> for i in RunTimeIterator():
...     print i
...
わーい！
たーのしー！
たーのしー！
>>> for i in RunTimeIterator():
...     print i
...
わーい！
たーのしー！
たーのしー！
```

元々、ある形式のファイルからヘッダーに当たる領域を何行かパースして、それ以降は行ごとに必要なデータだけ取って返すパーサーを、`file`を継承したクラスで書きたくて`next`メソッドを置き換えるなんてことを試していたらこの問題に遭遇してしまった。

#参考リンク
[^1]: [PEP 3114 -- Renaming iterator.next() to iterator.\_\_next__()](https://www.python.org/dev/peps/pep-3114/#double-underscore-methods-and-built-in-functions "PEP 3114 -- Renaming iterator.next() to iterator.__next__()")
[^2]: [python - Overriding the instance next function - Stack Overflow](https://stackoverflow.com/questions/41488230/overriding-the-instance-next-function "python - Overriding the instance next function - Stack Overflow")