Bisakah iterator diatur ulang dengan Python?

130

Bisakah saya mengatur ulang iterator / generator dengan Python? Saya menggunakan DictReader dan ingin mengatur ulang ke awal file.

codeforester
sumber
1
Kemungkinan duplikat dari Mereset objek generator dengan Python
sschuberth
Di samping catatan, saya menemukan bahwa list()fungsi akan mengulangi argumennya (an iterable). Jadi memanggil list()pada iterable yang sama dua kali (misalnya hasil dari zip()) Anda akan mendapatkan daftar kosong pada panggilan kedua!
theaws.blog

Jawaban:

84

Saya melihat banyak jawaban yang menyarankan itertools.tee , tetapi itu mengabaikan satu peringatan penting di dokumen untuk itu:

Alat itert ini mungkin memerlukan penyimpanan tambahan yang signifikan (tergantung pada seberapa banyak data sementara perlu disimpan). Secara umum, jika satu iterator menggunakan sebagian besar atau semua data sebelum iterator lain dimulai, itu lebih cepat digunakan list()daripada tee().

Pada dasarnya, teedirancang untuk situasi di mana dua (atau lebih) klon dari satu iterator, sementara "keluar dari sinkronisasi" satu sama lain, tidak melakukannya dengan banyak - sebaliknya, mereka mengatakan di "sekitar" yang sama (a beberapa item di belakang atau di depan satu sama lain). Tidak cocok untuk masalah OP "ulangi dari awal".

L = list(DictReader(...))di sisi lain sangat cocok, selama daftar dicts bisa masuk dengan nyaman dalam memori. Sebuah "iterator dari awal" (sangat ringan dan overhead rendah) dapat dibuat kapan saja dengan iter(L), dan digunakan sebagian atau seluruhnya tanpa mempengaruhi yang baru atau yang sudah ada; pola akses lainnya juga tersedia dengan mudah.

Seperti yang dikatakan beberapa jawaban dengan benar, dalam kasus tertentu csvAnda juga dapat .seek(0)objek file yang mendasarinya (kasus yang agak khusus). Saya tidak yakin itu didokumentasikan dan dijamin, meskipun saat ini berfungsi; itu mungkin akan layak dipertimbangkan hanya untuk file csv yang benar-benar besar, di mana listsaya merekomendasikan sebagai pendekatan umum akan memiliki jejak memori yang terlalu besar.

Alex Martelli
sumber
6
Menggunakan list()untuk cache multipassage melalui csvreader pada file 5MB melihat runtime saya pergi dari ~ 12secs ke ~ 0.5s.
John Mee
33

Jika Anda memiliki file csv bernama 'blah.csv' Sepertinya

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

Anda tahu bahwa Anda dapat membuka file untuk membaca, dan membuat DictReader dengan

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

Kemudian, Anda akan bisa mendapatkan baris berikutnya dengan reader.next(), yang seharusnya menghasilkan

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

menggunakannya lagi akan menghasilkan

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

Namun, pada titik ini jika Anda menggunakan blah.seek(0), lain kali Anda menelepon reader.next()Anda akan mendapatkan

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

lagi.

Tampaknya ini adalah fungsi yang Anda cari. Saya yakin ada beberapa trik yang terkait dengan pendekatan ini yang tidak saya sadari. @ Brian menyarankan untuk membuat DictReader lain. Ini tidak akan berfungsi jika Anda adalah pembaca pertama setengah jalan membaca file, karena pembaca baru Anda akan memiliki kunci dan nilai yang tidak terduga dari mana pun Anda berada dalam file.

Wilduck
sumber
Inilah yang dikatakan teori saya, senang melihat apa yang saya pikir harus terjadi, ternyata.
Wayne Werner
@Wilduck: perilaku yang Anda gambarkan dengan contoh lain DictReader tidak akan terjadi jika Anda membuat pegangan file baru dan meneruskannya ke DictReader kedua, bukan?
Jika Anda memiliki dua penangan file, mereka akan berperilaku secara independen, ya.
Wilduck
24

Tidak. Protokol iterator Python sangat sederhana, dan hanya menyediakan satu metode ( .next()atau __next__()), dan tidak ada metode untuk mereset iterator secara umum.

Pola yang umum adalah membuat iterator baru menggunakan prosedur yang sama lagi.

Jika Anda ingin "menyimpan" sebuah iterator sehingga Anda dapat kembali ke awal, Anda juga dapat melakukan fork iterator dengan menggunakan itertools.tee

u0b34a0f6ae
sumber
1
Meskipun analisis Anda tentang metode .next () mungkin benar, ada cara yang cukup sederhana untuk mendapatkan apa yang diminta oleh operasi.
Wilduck
2
@ Wilduck: Saya melihat bahwa jawaban Anda. Saya baru saja menjawab pertanyaan iterator, dan saya tidak tahu tentang csvmodulnya. Semoga kedua jawaban tersebut bermanfaat untuk poster aslinya.
u0b34a0f6ae
Secara ketat, protokol iterator juga membutuhkan __iter__. Artinya, iterator juga diharuskan untuk menjadi iterable.
Steve Jessop
11

Iya , jika Anda menggunakan numpy.nditeruntuk membangun iterator Anda.

>>> lst = [1,2,3,4,5]
>>> itr = numpy.nditer([lst])
>>> itr.next()
1
>>> itr.next()
2
>>> itr.finished
False
>>> itr.reset()
>>> itr.next()
1
Pengembang
sumber
Bisakah nditersiklus melalui array seperti itertools.cycle?
LWZ
1
@LWZ: Saya tidak berpikir begitu, tetapi Anda dapat try:dengan next()dan pada StopIterationpengecualian melakukan reset().
Dijeda sampai pemberitahuan lebih lanjut.
... diikuti dengannext()
Dijeda hingga pemberitahuan lebih lanjut.
Inilah yang saya cari!
sriram
1
Perhatikan bahwa batas "operan" di sini adalah 32: stackoverflow.com/questions/51856685/…
Simon
11

Ada bug dalam penggunaan .seek(0)seperti yang dianjurkan oleh Alex Martelli dan Wilduck di atas, yaitu panggilan berikutnya ke .next()akan memberikan kamus baris header Anda dalam bentuk {key1:key1, key2:key2, ...}. Solusinya adalah mengikuti file.seek(0)dengan panggilan untuk reader.next()menyingkirkan baris header.

Jadi kode Anda akan terlihat seperti ini:

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)
Steven Rumbalski
sumber
5

Ini mungkin ortogonal dengan pertanyaan awal, tetapi iterator dapat digabungkan dalam fungsi yang mengembalikan iterator.

def get_iter():
    return iterator

Untuk mengatur ulang iterator cukup panggil fungsinya lagi. Ini tentu saja sepele jika fungsi saat fungsi tersebut tidak membutuhkan argumen.

Jika fungsinya memerlukan beberapa argumen, gunakan functools.pihak untuk membuat penutupan yang bisa dilewatkan sebagai ganti iterator asli.

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

Ini tampaknya untuk menghindari caching yang perlu dilakukan oleh tee (n salinan) atau daftar (1 salinan)

Anish
sumber
3

Untuk file kecil, Anda dapat mempertimbangkan untuk menggunakan more_itertools.seekable- alat pihak ketiga yang menawarkan pengulangan ulang.

Demo

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))

Keluaran

{'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'}

Di sini a DictReaderdibungkus dengan seekableobjek (1) dan lanjutan (2). Ituseek() metode yang digunakan untuk me-reset / mundur iterator ke posisi 0 (3).

Catatan: konsumsi memori bertambah dengan iterasi, jadi berhati-hatilah dalam menerapkan alat ini ke file besar, seperti yang ditunjukkan dalam dokumen .

pylang
sumber
2

Meskipun tidak ada reset iterator, modul "itertools" dari python 2.6 (dan yang lebih baru) memiliki beberapa utilitas yang dapat membantu di sana. Salah satunya adalah "tee" yang dapat membuat banyak salinan dari sebuah iterator, dan menyimpan hasil dari iterator yang berjalan di depan, sehingga hasil ini digunakan pada salinan. Saya akan melihat tujuan Anda:

>>> 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]
jsbueno
sumber
1

Untuk DictReader:

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

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

Untuk 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()
mAsT3RpEE
sumber
1

list(generator()) mengembalikan semua nilai yang tersisa untuk generator dan secara efektif menyetel ulang jika tidak diulang.

Will Dereham
sumber
1

Masalah

Saya pernah mengalami masalah yang sama sebelumnya. Setelah menganalisis kode saya, saya menyadari bahwa mencoba mengatur ulang iterator di dalam loop sedikit meningkatkan kompleksitas waktu dan itu juga membuat kode sedikit jelek.

Larutan

Buka file dan simpan baris ke variabel dalam memori.

# 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)

Sekarang Anda dapat melakukan loop melalui baris di mana saja dalam lingkup Anda tanpa berurusan dengan iterator.

Anthony Holloman
sumber
1

Salah satu opsi yang memungkinkan adalah menggunakan itertools.cycle(), yang memungkinkan Anda mengulang tanpa batas waktu tanpa trik seperti itu .seek(0).

iterDic = itertools.cycle(csv.DictReader(open('file.csv')))
Greg H
sumber
1

Saya sampai pada masalah yang sama ini - sementara saya menyukai tee()solusinya, saya tidak tahu seberapa besar file saya nantinya dan peringatan memori tentang mengkonsumsi satu terlebih dahulu sebelum yang lain membuat saya tidak bisa mengadopsi metode itu.

Sebagai gantinya, saya membuat sepasang iterator menggunakan iter()pernyataan, dan menggunakan yang pertama untuk proses awal saya, sebelum beralih ke yang kedua untuk proses terakhir.

Jadi, dalam kasus dict-reader, jika reader didefinisikan menggunakan:

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

Saya dapat membuat sepasang iterator dari "spesifikasi" ini - menggunakan:

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

Saya kemudian dapat menjalankan kode akses pertama saya d1, aman karena mengetahui bahwa iterator kedua d2telah ditentukan dari spesifikasi root yang sama.

Saya belum menguji ini secara menyeluruh, tetapi tampaknya berfungsi dengan data tiruan.

Thomas Kimber
sumber
0

Hanya jika tipe yang mendasari menyediakan mekanisme untuk melakukannya (misalnya fp.seek(0)).

Ignacio Vazquez-Abrams
sumber
0

Kembalikan iterator yang baru dibuat pada iterasi terakhir selama panggilan '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=' ')

Keluaran:

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 
nry
sumber