Pythonのイテレータをリセット・コピー可能にする

Pythonのイテレータは、一度取り出し始めると、ゼロの位置などへリセットが出来ないそうで、

> イテレータをリセットしたり、コピーを作ったりする方法はありません。:
> https://docs.python.org/ja/3/howto/functional.html

そのため、イテレータをリセット・コピー可能にするWrapperクラス`ResetableGtor`を用意しました。動作は、単純に、イテレータから取り出した要素を、リストに保存していき、リセットして前へ戻す時には、リストから以前の要素のデータを取り出すだけになります。

# イテレータをリセット・コピー可能にする試行コード

```python:Pythonコード
import random

# sec: main

def main(): # 実行切替用
    run__main1()
    pass

# イテレータをリセット・コピー可能にする試行
def run__main1():
    
    # sec: prepare

    def gen_test_gtor():
        i = 0
        while True:
            yield i, random.random() # テスト用に、(順番の番号, 乱数)
            i += 1
    
    gtor_orig = gen_test_gtor()
    gtor_reset = ResetableGtor(gtor_orig)
    
    # sec: 始めに一度取得
    
    print("始めに一度取得")
    print(next(gtor_reset))
    print(next(gtor_reset))
    print(next(gtor_reset))

    # sec: リセットして再取得
    
    print("リセットして再取得")
    gtor_reset.reset()
    print(next(gtor_reset))
    print(next(gtor_reset))
    print(next(gtor_reset))
    print(next(gtor_reset))
    print(next(gtor_reset))

    # sec: 任意位置へリセット
    
    print("任意位置へリセット(3へ)")
    gtor_reset.reset(3)
    print(next(gtor_reset))
    print(next(gtor_reset))
    print(next(gtor_reset))

    # sec: コピーしたもの
    
    print("コピーしたもの")
    gtor_reset2 = gtor_reset.copy()
    gtor_reset2.reset()
    print(next(gtor_reset2))
    print(next(gtor_reset2))
    print(next(gtor_reset2))
    
    # sec: コピーしたものをリセットして再取得
    
    print("コピーしたものをリセットして再取得")
    gtor_reset2.reset()
    print(next(gtor_reset2))
    print(next(gtor_reset2))
    print(next(gtor_reset2))
    
    # sec: コピー元から引き続き取得
    
    print("コピー元から引き続き取得")
    print(next(gtor_reset))
    print(next(gtor_reset))
    print(next(gtor_reset))
    
    # sec: リセット機能を終了し、引き続き取得
    
    print("リセット機能を終了し、引き続き取得")
    gtor_reset.end_reset()
    print(next(gtor_reset))
    print(next(gtor_reset))
    print(next(gtor_reset))

    # sec: コピーしたものをリセットして再取得(まだリセット可能)
    
    print("コピーしたものをリセットして再取得(まだリセット可能)")
    gtor_reset2.reset()
    print(next(gtor_reset2))
    print(next(gtor_reset2))
    print(next(gtor_reset2))

# イテレータをリセット・コピー可能にするクラス、Wrapper
class ResetableGtor:
    # HACK: イテレータを試しに取り出すとリセットできない為、Wrapperを作成
    # ref: イテレータをリセットしたり、コピーを作ったりする方法はありません。
    # https://docs.python.org/ja/3/howto/functional.html
    
    def __init__(self, gtor, taken_list=[], i_next=0):
        self.gtor_orig = gtor
        self.taken_list = taken_list # 既に取り出したもの
        self.i_next = i_next
    
    def __next__(self):
        
        if self.i_next is None: # if: リセット機能終了
            return next(self.gtor_orig)
        
        elif self.i_next < len(self.taken_list): # if: iが既に取り出したものの中
            item_i = self.taken_list[self.i_next]
        
        else: # if: iが新しいものの中
            item_i = next(self.gtor_orig)
            self.taken_list.append(item_i)
        
        self.i_next += 1
        return item_i
    
    def reset(self, i_next=0):
    
        if i_next <= len(self.taken_list): # if: iが既に取り出したものの中、または、新しい次の1個
            self.i_next = i_next
        else:
            raise IndexError("既に取り出したものの間でのみリセット可" + f"(0～{len(self.taken_list)})")
    
    # リセット機能を終了する、これ以降はリセット不可、内部保持のリストを解放して省メモリ
    def end_reset(self):
        self.taken_list = None
        self.i_next = None
    
    # ジェネレータをコピー、内部保持のリストは共通として省メモリ
    def copy(self):
        return ResetableGtor(self.gtor_orig, self.taken_list, self.i_next)


# sec: entry

if __name__ == "__main__": main()
```

# 当コード実行時のコンソール出力

```一度取得
始めに一度取得
(0, 0.1422823753871918)
(1, 0.7957444612115221)
(2, 0.7467023469338347)
リセットして再取得
(0, 0.1422823753871918)
(1, 0.7957444612115221)
(2, 0.7467023469338347)
(3, 0.4969213126221237)
(4, 0.7025567328572484)
任意位置へリセット(3へ)
(3, 0.4969213126221237)
(4, 0.7025567328572484)
(5, 0.5619421955074381)
コピーしたもの
(0, 0.1422823753871918)
(1, 0.7957444612115221)
(2, 0.7467023469338347)
コピーしたものをリセットして再取得
(0, 0.1422823753871918)
(1, 0.7957444612115221)
(2, 0.7467023469338347)
コピー元から引き続き取得
(6, 0.3426861200020309)
(7, 0.16324882859285683)
(8, 0.4264613547755832)
リセット機能を終了し、引き続き取得
(9, 0.26693377512603544)
(10, 0.027910806234261454)
(11, 0.8334200438449296)
コピーしたものをリセットして再取得(まだリセット可能)
(0, 0.1422823753871918)
(1, 0.7957444612115221)
(2, 0.7467023469338347)
```

# 参照

イテレータをリセットしたり、コピーを作ったりする方法はありません。:
https://docs.python.org/ja/3/howto/functional.html

Python で iterator を使うときに考える初期化のタイミング:
https://qiita.com/tfull_tf/items/3f988300cb27d4b404ec

イテラブルやイテレータとは:
https://qiita.com/bkh4149/items/fa7c80e4d7077aa609c1

【Python】イテレータについて:
https://qiita.com/sugimochi_1019/items/aad6d002fa8d49b8bbdf

【Python】イテレータとは:
https://qiita.com/HookasSariSat/items/32f31cebb55db92f708c