ตัวทำซ้ำสามารถรีเซ็ตใน Python ได้หรือไม่?


130

ฉันสามารถรีเซ็ตตัววนซ้ำ / ตัวสร้างใน Python ได้หรือไม่ ฉันใช้ DictReader และต้องการรีเซ็ตเป็นจุดเริ่มต้นของไฟล์



ในบันทึกด้านข้างฉันพบว่าlist()ฟังก์ชันจะวนซ้ำผ่านอาร์กิวเมนต์ (ทำซ้ำได้) ดังนั้นการโทรlist()ซ้ำสองครั้ง (เช่นผลลัพธ์zip()) คุณจะได้รับรายการว่างในการโทรครั้งที่สอง!
theaws.blog

คำตอบ:


84

ฉันเห็นคำตอบมากมายที่แนะนำitertools.teeแต่นั่นเป็นการเพิกเฉยต่อคำเตือนที่สำคัญอย่างหนึ่งในเอกสาร:

itertool นี้อาจต้องการพื้นที่จัดเก็บข้อมูลสำรองที่สำคัญ (ขึ้นอยู่กับว่าต้องจัดเก็บข้อมูลชั่วคราวมากแค่ไหน) โดยทั่วไปถ้าหนึ่ง iterator ใช้ส่วนใหญ่หรือทั้งหมดของข้อมูลก่อนอื่น iterator เริ่มต้นก็เป็นได้เร็วขึ้นเพื่อใช้แทนlist()tee()

โดยทั่วไปteeได้รับการออกแบบมาสำหรับสถานการณ์เหล่านั้นที่มีตัวทำซ้ำสอง (หรือมากกว่า) ของตัววนซ้ำหนึ่งตัวในขณะที่ "ออกจากการซิงค์" ซึ่งกันและกันอย่าทำอย่างนั้นมากนักแต่จะพูดใน "บริเวณใกล้เคียง" เดียวกัน (ก ไม่กี่รายการข้างหลังหรือข้างหน้า) ไม่เหมาะกับปัญหา OP ของ "ทำซ้ำตั้งแต่เริ่มต้น"

L = list(DictReader(...))ในทางกลับกันเหมาะอย่างยิ่งตราบใดที่รายการคำสั่งสามารถใส่ลงในหน่วยความจำได้อย่างสะดวกสบาย "ตัววนซ้ำตั้งแต่เริ่มต้น" ใหม่ (น้ำหนักเบามากและมีค่าใช้จ่ายต่ำ) สามารถทำได้ตลอดเวลาiter(L)และใช้เพียงบางส่วนหรือทั้งหมดโดยไม่ส่งผลกระทบต่อสิ่งใหม่หรือที่มีอยู่ รูปแบบการเข้าถึงอื่น ๆ สามารถใช้ได้อย่างง่ายดาย

ตามที่หลาย ๆ คำตอบได้กล่าวไว้อย่างถูกต้องในกรณีเฉพาะของcsvคุณยังสามารถ.seek(0)ใช้อ็อบเจ็กต์ไฟล์ที่เป็นพื้นฐาน (เป็นกรณีที่ค่อนข้างพิเศษ) ฉันไม่แน่ใจว่าเป็นเอกสารและรับประกันแม้ว่าจะใช้งานได้ในขณะนี้ มันอาจจะคุ้มค่าที่จะพิจารณาเฉพาะสำหรับไฟล์ csv ขนาดใหญ่อย่างแท้จริงซึ่งlistฉันขอแนะนำใหม่เนื่องจากวิธีการทั่วไปจะมีขนาดหน่วยความจำที่ใหญ่เกินไป


6
การใช้list()แคชมัลติพาสเซจผ่าน csvreader บนไฟล์ 5MB ทำให้รันไทม์ของฉันเปลี่ยนไปจาก ~ 12 วินาทีถึง ~ 0.5 วินาที
John Mee

33

หากคุณมีไฟล์ csv ชื่อ 'blah.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}

อีกครั้ง

ดูเหมือนว่าจะเป็นฟังก์ชันที่คุณกำลังมองหา ฉันแน่ใจว่ามีเทคนิคบางอย่างที่เกี่ยวข้องกับแนวทางนี้ซึ่งฉันไม่รู้ @ ไบรอันแนะนำเพียงแค่สร้าง DictReader อื่น วิธีนี้จะไม่ได้ผลหากคุณเป็นผู้อ่านคนแรกอยู่ครึ่งทางในการอ่านไฟล์เนื่องจากโปรแกรมอ่านใหม่ของคุณจะมีคีย์และค่าที่ไม่คาดคิดจากทุกที่ที่คุณอยู่ในไฟล์


นี่คือสิ่งที่ทฤษฎีของฉันบอกฉันดีใจที่ได้เห็นสิ่งที่ฉันคิดว่าควรจะเกิดขึ้น
Wayne Werner

@ Wilduck: พฤติกรรมที่คุณอธิบายด้วยอินสแตนซ์อื่นของ DictReader จะไม่เกิดขึ้นหากคุณสร้างไฟล์ใหม่และส่งต่อไปยัง DictReader ตัวที่สองใช่ไหม

หากคุณมีตัวจัดการไฟล์สองตัวพวกเขาจะทำงานแยกกันใช่
Wilduck

24

ไม่โปรโตคอลตัววนซ้ำของ Python นั้นง่ายมากและมีวิธีเดียว ( .next()หรือ__next__()) เพียงวิธีเดียวและไม่มีวิธีการรีเซ็ตตัววนซ้ำโดยทั่วไป

รูปแบบทั่วไปคือการสร้างตัววนซ้ำใหม่แทนโดยใช้ขั้นตอนเดิมอีกครั้ง

หากคุณต้องการ "บันทึก" ตัววนซ้ำเพื่อให้คุณกลับไปที่จุดเริ่มต้นได้คุณสามารถแยกตัววนซ้ำได้โดยใช้ itertools.tee


1
ในขณะที่คุณกำลังวิเคราะห์วิธีการ. next () อาจถูกต้อง แต่ก็มีวิธีที่ค่อนข้างง่ายในการรับสิ่งที่ฝ่ายปฏิบัติการต้องการ
Wilduck

2
@ วิลดูค: ฉันเห็นว่าคำตอบของคุณ ฉันเพิ่งตอบคำถาม iterator และฉันไม่รู้เกี่ยวกับcsvโมดูลนี้ หวังว่าทั้งสองคำตอบจะเป็นประโยชน์กับผู้โพสต์ต้นฉบับ
u0b34a0f6ae

อย่างเคร่งครัดโปรโตคอล iterator __iter__ยังต้อง นั่นคือต้องมีตัวทำซ้ำเพื่อเป็นซ้ำ
Steve Jessop

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

1
@LWZ: ผมไม่คิดอย่างนั้น แต่คุณสามารถและในข้อยกเว้นทำ try:next()StopIterationreset()
หยุดชั่วคราวจนกว่าจะมีประกาศอีกครั้ง


นี่คือสิ่งที่ฉันกำลังมองหา!
sriram

1
โปรดทราบว่าขีด จำกัด ของ "ตัวถูกดำเนินการ" ที่นี่คือ 32: stackoverflow.com/questions/51856685/…
Simon

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)

ดูเหมือนว่าจะหลีกเลี่ยงการแคชที่ tee (n copy) หรือ list (1 copy) จะต้องทำ


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

นี่DictReaderคือห่อด้วยseekableวัตถุ (1) และขั้นสูง (2) seek()วิธีการที่ใช้ในการตั้งค่า / ย้อนกลับ iterator ไปที่ตำแหน่ง 0 (ที่ 3)

หมายเหตุ: การใช้หน่วยความจำเติบโตด้วยซ้ำดังนั้นจึงต้องระมัดระวังในการใช้เครื่องมือนี้เพื่อไฟล์ขนาดใหญ่เช่นที่ระบุไว้ในเอกสาร


2

แม้ว่าจะไม่มีการรีเซ็ตตัววนซ้ำโมดูล "itertools" จาก python 2.6 (และใหม่กว่า) มียูทิลิตี้บางอย่างที่สามารถช่วยได้ หนึ่งในนั้นคือ "ที" ซึ่งสามารถสร้างสำเนาของเครื่องวนซ้ำได้หลายชุดและแคชผลลัพธ์ของเครื่องที่ทำงานอยู่ข้างหน้าเพื่อให้ใช้ผลลัพธ์เหล่านี้กับสำเนา ฉันจะตัดวัตถุประสงค์ของคุณ:

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

ทางเลือกหนึ่งที่เป็นไปได้คือการใช้ itertools.cycle()ซึ่งจะช่วยให้คุณไปเรื่อย ๆ โดยไม่ต้องย้ำเคล็ดลับใด ๆ .seek(0)เช่น

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

1

ฉันมาถึงปัญหาเดียวกันนี้ - ในขณะที่ฉันชอบไฟล์ tee()วิธีแก้ปัญหาฉันไม่รู้ว่าไฟล์ของฉันจะมีขนาดใหญ่แค่ไหนและคำเตือนเกี่ยวกับหน่วยความจำเกี่ยวกับการบริโภคไฟล์แรกก่อนที่อีกไฟล์จะทำให้ฉันเลิกใช้วิธีนี้

แต่ฉันกำลังสร้างตัววนซ้ำโดยใช้ไฟล์ iter()คำสั่งและใช้ตัวแรกสำหรับการรันครั้งแรกก่อนที่จะเปลี่ยนไปใช้ตัวที่สองสำหรับการรันครั้งสุดท้าย

ดังนั้นในกรณีของเครื่องอ่านคำสั่งหากผู้อ่านถูกกำหนดโดยใช้:

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

ฉันสามารถสร้างตัวทำซ้ำคู่หนึ่งจาก "ข้อกำหนด" นี้ - โดยใช้:

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

จากนั้นฉันสามารถรันรหัส 1st-pass ของฉันได้d1โดยปลอดภัยเมื่อทราบว่าตัววนซ้ำตัวที่สองd2ถูกกำหนดจากข้อกำหนดรูทเดียวกัน

ฉันไม่ได้ทดสอบอย่างละเอียดถี่ถ้วน แต่ดูเหมือนว่าจะใช้ได้กับข้อมูลจำลอง


1

ส่งคืนตัววนซ้ำที่สร้างขึ้นใหม่ในการทำซ้ำครั้งสุดท้ายระหว่างการเรียก "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.