Pythonでイテレータをリセットできますか?


回答:


83

itertools.teeを示唆する多くの回答が見られますが、それはドキュメントの重要な警告を無視しています:

このitertoolは、かなりの補助ストレージを必要とする場合があります(保存する必要がある一時データの量によって異なります)。一般に、あるイテレーターが別のイテレーターが開始する前にほとんどまたはすべてのデータを使用する場合、のlist()代わりに使用する方が高速ですtee()

基本的にteeは、1つのイテレータの2つ(またはそれ以上)のクローンが互いに「同期が取れていない」が、それほど多くは行わないような状況向けに設計されています。むしろ、同じ「近傍」(aいくつかのアイテムが前後に並んでいます)。OPの「最初からやり直す」という問題には適していません。

L = list(DictReader(...))一方、辞書のリストがメモリに快適に収まる限り、完全に適しています。新しい「最初からのイテレータ」(非常に軽量でオーバーヘッドが少ない)はiter(L)、を使用していつでも作成でき、新規または既存のものに影響を与えずに部分的または全体的に使用できます。他のアクセスパターンも簡単に利用できます。

いくつかの回答が正しく述べているように、特定のケースでは、基礎となるファイルオブジェクトcsvも可能.seek(0)です(かなり特殊なケース)。文書化され保証されているかどうかはわかりませんが、現在は機能しています。本当に巨大なcsvファイルについてのみ検討する価値listがあります。一般的なアプローチではメモリフットプリントが大きすぎるため、このファイルを推奨します。


6
list()5MBのファイルでcsvreaderを介してマルチパッセージをキャッシュするために使用すると、ランタイムが約12秒から約0.5秒になることがわかります。
ジョン・ミー

33

'blah.csv'という名前のcsvファイルがある場合

a,b,c,d
1,2,3,4
2,3,4,5
3,4,5,6

読み取り用にファイルを開いて、DictReaderを作成できることを知っています。

blah = open('blah.csv', 'r')
reader= csv.DictReader(blah)

次に、次の行をreader.next()で取得できます。

{'a':1,'b':2,'c':3,'d':4}

もう一度使うと

{'a':2,'b':3,'c':4,'d':5}

ただし、この時点でを使用するblah.seek(0)と、次に電話したreader.next()ときに

{'a':1,'b':2,'c':3,'d':4}

再び。

これはあなたが探している機能のようです。私が気づいていないこのアプローチに関連するいくつかのトリックがあると確信しています。@Brianは、単に別のDictReaderを作成することを提案しました。新しいリーダーは、ファイル内のどこからでも予期しないキーと値を取得するため、最初のリーダーがファイルの読み取りの途中である場合、これは機能しません。


これは私の理論が私に言ったことであり、私が起こるべきであると思っていたことが実行するのを見てうれしいです。
ウェインヴェルナー

@Wilduck:新しいファイルハンドルを作成し、それを2番目のDictReaderに渡した場合、DictReaderの別のインスタンスで記述している動作は発生しません。

2つのファイルハンドラーがある場合、それらは独立して動作します(はい)。
Wilduck 2012年

24

いいえ、できません。Pythonのイテレータプロトコルは非常にシンプルで、1つのメソッド(.next()または__next__())のみを提供し、イテレータをリセットするメソッドは通常ありません。

一般的なパターンは、代わりに同じ手順を使用して新しいイテレータを作成することです。

イテレータを「保存」して、最初に戻ることができるようにする場合は、次のコマンドを使用してイテレータをフォークすることもできます。 itertools.tee


1
.next()メソッドの分析はおそらく正しいですが、操作が要求するものを取得するためのかなり単純な方法があります。
Wilduck

2
@ワイルドカック:私はあなたの答えを見る。イテレータの質問に答えたところですが、csvモジュールについて何も知りません。うまくいけば、両方の答えが元のポスターに役立ちます。
u0b34a0f6ae 2010

厳密には、イテレータプロトコルにはも必要__iter__です。つまり、反復子は反復可能である必要もあります。
スティーブジェソップ

11

はいnumpy.nditerイテレータの作成に使用する場合。

>>> lst = [1,2,3,4,5]
>>> itr = numpy.nditer([lst])
>>> itr.next()
1
>>> itr.next()
2
>>> itr.finished
False
>>> itr.reset()
>>> itr.next()
1

nditerように配列を循環できますitertools.cycleか?
LWZ 2013

1
@LWZ:私はそうは思わないが、あなたがすることができますし、上の例外ください。try:next()StopIterationreset()
追って通知があるまで一時停止。

...後に続くnext()
通知があるまで一時停止。

これは私が探していたものです!
スリラム

1
:「オペランド」の限界はここ32であることに注意してくださいstackoverflow.com/questions/51856685/...
サイモン

11

.seek(0)上記のAlex MartelliとWilduckが提唱しているように、を使用するとバグが発生します。つまり、への次の呼び出し.next()で、ヘッダー行の辞書がの形式で返されます{key1:key1, key2:key2, ...}。回避策は、ヘッダー行を取り除くためにをfile.seek(0)呼び出すことreader.next()です。

したがって、コードは次のようになります。

f_in = open('myfile.csv','r')
reader = csv.DictReader(f_in)

for record in reader:
    if some_condition:
        # reset reader to first row of data on 2nd line of file
        f_in.seek(0)
        reader.next()
        continue
    do_something(record)

5

これはおそらく元の質問と直交していますが、反復子を返す関数で反復子をラップすることもできます。

def get_iter():
    return iterator

イテレータをリセットするには、関数をもう一度呼び出します。もちろん、関数が引数を取らない場合、関数は取るに足らないものです。

関数に引数が必要な場合は、functools.partialを使用して、元のイテレーターの代わりに渡すことができるクロージャーを作成します。

def get_iter(arg1, arg2):
   return iterator
from functools import partial
iter_clos = partial(get_iter, a1, a2)

これは、ティー(nコピー)またはリスト(1コピー)が実行する必要があるキャッシュを回避するようです


3

小さなファイルの場合、more_itertools.seekableイテラブルのリセットを提供するサードパーティのツールの使用を検討できます。

デモ

import csv

import more_itertools as mit


filename = "data/iris.csv"
with open(filename, "r") as f:
    reader = csv.DictReader(f)
    iterable = mit.seekable(reader)                    # 1
    print(next(iterable))                              # 2
    print(next(iterable))
    print(next(iterable))

    print("\nReset iterable\n--------------")
    iterable.seek(0)                                   # 3
    print(next(iterable))
    print(next(iterable))
    print(next(iterable))

出力

{'Sepal width': '3.5', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '5.1', 'Species': 'Iris-setosa'}
{'Sepal width': '3', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '4.9', 'Species': 'Iris-setosa'}
{'Sepal width': '3.2', 'Petal width': '0.2', 'Petal length': '1.3', 'Sepal length': '4.7', 'Species': 'Iris-setosa'}

Reset iterable
--------------
{'Sepal width': '3.5', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '5.1', 'Species': 'Iris-setosa'}
{'Sepal width': '3', 'Petal width': '0.2', 'Petal length': '1.4', 'Sepal length': '4.9', 'Species': 'Iris-setosa'}
{'Sepal width': '3.2', 'Petal width': '0.2', 'Petal length': '1.3', 'Sepal length': '4.7', 'Species': 'Iris-setosa'}

ここで、a DictReaderseekableオブジェクトにラップされ(1)、高度な(2)です。このseek()メソッドは、イテレーターを0番目の位置(3)にリセット/巻き戻すために使用されます。

注:メモリの消費量は繰り返しによって増加するため、docsに示されているように、このツールを大きなファイルに適用する場合は注意してください。


2

イテレータのリセットはありませんが、Python 2.6以降の「itertools」モジュールには、そこで役立つユーティリティがいくつかあります。その1つは、イテレータの複数のコピーを作成し、先に実行されているものの結果をキャッシュして、これらの結果がコピーで使用される「ティー」です。私はあなたの目的を分別します:

>>> def printiter(n):
...   for i in xrange(n):
...     print "iterating value %d" % i
...     yield i

>>> from itertools import tee
>>> a, b = tee(printiter(5), 2)
>>> list(a)
iterating value 0
iterating value 1
iterating value 2
iterating value 3
iterating value 4
[0, 1, 2, 3, 4]
>>> list(b)
[0, 1, 2, 3, 4]

1

DictReaderの場合:

f = open(filename, "rb")
d = csv.DictReader(f, delimiter=",")

f.seek(0)
d.__init__(f, delimiter=",")

DictWriterの場合:

f = open(filename, "rb+")
d = csv.DictWriter(f, fieldnames=fields, delimiter=",")

f.seek(0)
f.truncate(0)
d.__init__(f, fieldnames=fields, delimiter=",")
d.writeheader()
f.flush()

1

list(generator()) ジェネレーターの残りのすべての値を返し、ループされていない場合は効果的にリセットします。


1

問題

以前にも同じ問題がありました。コードを分析した後、ループ内でイテレーターをリセットしようとすると、時間の複雑さが少し増加し、コードが少し見苦しくなります。

解決

ファイルを開き、行をメモリ内の変数に保存します。

# initialize list of rows
rows = []

# open the file and temporarily name it as 'my_file'
with open('myfile.csv', 'rb') as my_file:

    # set up the reader using the opened file
    myfilereader = csv.DictReader(my_file)

    # loop through each row of the reader
    for row in myfilereader:
        # add the row to the list of rows
        rows.append(row)

これで、イテレータを扱わなくても、スコープ内の任意の場所でをループできます。


1

可能なオプションの1つは、を使用itertools.cycle()することです。これにより、のようなトリックなしで無期限に反復することができます.seek(0)

iterDic = itertools.cycle(csv.DictReader(open('file.csv')))

1

私はこれと同じ問題にたどり着きました- tee()解決策は好きですが、ファイルがどれくらいの大きさになるかわからないので、最初に1つを消費することに関するメモリ警告が出て、他の方法がその方法の採用を先延ばしにしています。

代わりに、iter()ステートメントを使用して1 組のイテレータを作成し、最初のランスルーに最初のイテレータを使用してから、最後のランに2番目のイテレータに切り替えます。

したがって、dict-readerの場合、リーダーが次のように定義されている場合:

d = csv.DictReader(f, delimiter=",")

この「仕様」からイテレータのペアを作成できます-以下を使用します:

d1, d2 = iter(d), iter(d)

次に、1番目のパスのコードをに対して実行できd1ます。2番目のイテレーターd2が同じルート仕様から定義されていることを知っているので安全です。

私はこれを徹底的にテストしていませんが、ダミーデータで動作するようです。



0

'iter()'呼び出し中の最後の反復で新しく作成された反復子を返します

class ResetIter: 
  def __init__(self, num):
    self.num = num
    self.i = -1

  def __iter__(self):
    if self.i == self.num-1: # here, return the new object
      return self.__class__(self.num) 
    return self

  def __next__(self):
    if self.i == self.num-1:
      raise StopIteration

    if self.i <= self.num-1:
      self.i += 1
      return self.i


reset_iter = ResetRange(10)
for i in reset_iter:
  print(i, end=' ')
print()

for i in reset_iter:
  print(i, end=' ')
print()

for i in reset_iter:
  print(i, end=' ')

出力:

0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9 
0 1 2 3 4 5 6 7 8 9 
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.