Có thể đặt lại trình vòng lặp bằng Python không?


Câu trả lời:


84

Tôi thấy nhiều câu trả lời đề xuất itertools.tee , nhưng đó là bỏ qua một cảnh báo quan trọng trong tài liệu cho nó:

Itertool này có thể yêu cầu bộ nhớ phụ trợ đáng kể (tùy thuộc vào lượng dữ liệu tạm thời cần được lưu trữ). Nói chung, nếu một trình lặp sử dụng hầu hết hoặc tất cả dữ liệu trước khi một trình lặp khác bắt đầu, thì việc sử dụng list()thay thế sẽ nhanh hơn tee().

Về cơ bản, teeđược thiết kế cho những trường hợp mà hai (hoặc nhiều) bản sao của một trình lặp, trong khi "không đồng bộ" với nhau, không làm như vậy nhiều - đúng hơn, chúng nói trong cùng một "vùng lân cận" (a vài mục đứng sau hoặc trước nhau). Không phù hợp với vấn đề "làm lại từ đầu" của OP.

L = list(DictReader(...))mặt khác là hoàn toàn phù hợp, miễn là danh sách các phái có thể nằm gọn trong bộ nhớ. Một "trình lặp lại từ đầu" mới (rất nhẹ và chi phí thấp) có thể được tạo bất kỳ lúc nào vớiiter(L) và được sử dụng một phần hoặc toàn bộ mà không ảnh hưởng đến những cái mới hoặc hiện có; các mẫu truy cập khác cũng có sẵn dễ dàng.

Như một số câu trả lời đã nhận xét đúng, trong trường hợp cụ thể của csvbạn cũng có thể là .seek(0)đối tượng tệp cơ bản (một trường hợp khá đặc biệt). Tôi không chắc điều đó được ghi chép và đảm bảo, mặc dù nó hiện đang hoạt động; nó có lẽ chỉ đáng được xem xét đối với các tệp csv thực sự lớn, trong đó listtôi khuyến cáo là phương pháp chung sẽ có dung lượng bộ nhớ quá lớn.


6
Sử dụng list()để cache multipassage qua csvreader trên tệp 5MB, thời gian chạy của tôi từ ~ 12 giây xuống ~ 0,5 giây.
John Mee

33

Nếu bạn có một tệp csv tên là 'blah.csv' Điều đó trông giống như

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

bạn biết rằng bạn có thể mở tệp để đọc và tạo một DictReader với

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

Sau đó, bạn sẽ có thể nhận được dòng tiếp theo, dòng reader.next()này sẽ xuất ra

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

sử dụng nó một lần nữa sẽ sản xuất

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

Tuy nhiên, tại thời điểm này nếu bạn sử dụng blah.seek(0), lần sau khi gọi reader.next()bạn sẽ nhận được

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

lần nữa.

Đây dường như là chức năng bạn đang tìm kiếm. Tuy nhiên, tôi chắc rằng có một số thủ thuật liên quan đến cách tiếp cận này mà tôi không biết. @Brian đề xuất chỉ cần tạo một DictReader khác. Điều này sẽ không hoạt động nếu trình đọc đầu tiên của bạn mới đọc được một nửa tệp, vì trình đọc mới của bạn sẽ có các khóa và giá trị không mong đợi từ bất kỳ nơi nào bạn đang ở trong tệp.


Đây là những gì lý thuyết của tôi đã nói với tôi, rất vui khi thấy rằng điều tôi nghĩ nên xảy ra, lại xảy ra.
Wayne Werner

@Wilduck: hành vi bạn đang mô tả với một phiên bản DictReader khác sẽ không xảy ra nếu bạn tạo một trình xử lý tệp mới và chuyển nó cho DictReader thứ hai, phải không?

Nếu bạn có hai trình xử lý tệp, chúng sẽ hoạt động độc lập, vâng.
Wilduck

24

Không. Giao thức trình vòng lặp của Python rất đơn giản và chỉ cung cấp một phương thức duy nhất ( .next()hoặc __next__()) và không có phương pháp nào để đặt lại trình vòng lặp nói chung.

Mẫu phổ biến là thay vào đó tạo một trình vòng lặp mới bằng cách sử dụng lại thủ tục tương tự.

Nếu bạn muốn "lưu" một trình vòng lặp để bạn có thể quay lại ban đầu của nó, bạn cũng có thể tách trình vòng lặp đó bằng cách sử dụng itertools.tee


1
Mặc dù bạn phân tích phương thức .next () có thể đúng, nhưng có một cách khá đơn giản để lấy những gì op đang yêu cầu.
Wilduck,

2
@Wilduck: Tôi thấy đó là câu trả lời của bạn. Tôi vừa trả lời câu hỏi về trình lặp và tôi không biết gì về csvmô-đun. Hy vọng rằng cả hai câu trả lời đều hữu ích cho người đăng ban đầu.
u0b34a0f6ae

Nghiêm ngặt, giao thức trình lặp cũng yêu cầu __iter__. Nghĩa là, các trình vòng lặp cũng được yêu cầu phải là các trình lặp.
Steve Jessop

11

Đúng , nếu bạn sử dụng numpy.nditerđể xây dựng trình lặp của mình.

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

Có thể nditerchu trình qua mảng như thế itertools.cyclenào?
LWZ

1
@LWZ: Tôi không nghĩ như vậy, nhưng bạn có thể try:sự next()và trên một StopIterationngoại lệ làm một reset().
Tạm dừng cho đến khi có thông báo mới.


Đây là những gì tôi đang tìm kiếm!
sriram

1
Lưu ý rằng giới hạn của "toán hạng" ở đây là 32: stackoverflow.com/questions/51856685/...
Simon

11

Có một lỗi trong việc sử dụng .seek(0)như được ủng hộ bởi Alex Martelli và Wilduck ở trên, cụ thể là cuộc gọi tiếp theo .next()sẽ cung cấp cho bạn một từ điển về hàng tiêu đề của bạn ở dạng {key1:key1, key2:key2, ...}. Công việc xung quanh là thực hiện theo file.seek(0)lời gọi để reader.next()loại bỏ hàng tiêu đề.

Vì vậy, mã của bạn sẽ trông giống như sau:

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

Điều này có lẽ là trực giao với câu hỏi ban đầu, nhưng người ta có thể bọc trình vòng lặp trong một hàm trả về trình vòng lặp.

def get_iter():
    return iterator

Để đặt lại trình lặp chỉ cần gọi lại hàm. Điều này tất nhiên là nhỏ nếu hàm khi hàm đã nói không có đối số.

Trong trường hợp hàm yêu cầu một số đối số, hãy sử dụng functools.partial để tạo một bao đóng có thể được truyền thay vì trình lặp ban đầu.

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

Điều này dường như để tránh bộ nhớ đệm mà tee (n bản sao) hoặc danh sách (1 bản sao) sẽ cần phải làm


3

Đối với các tệp nhỏ, bạn có thể cân nhắc sử dụng more_itertools.seekable- một công cụ của bên thứ ba cung cấp khả năng đặt lại các tệp lặp.

Bản giới thiệu

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

Đầu ra

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

Ở đây a DictReaderđược bao bọc trong seekableđối tượng (1) và nâng cao (2). Cácseek() phương pháp được sử dụng để thiết lập lại / tua lại lặp đến vị trí 0 (3).

Lưu ý: mức tiêu thụ bộ nhớ tăng lên khi lặp lại, vì vậy hãy thận trọng khi áp dụng công cụ này cho các tệp lớn, như được chỉ ra trong tài liệu .


2

Mặc dù không có thiết lập lại trình lặp, mô-đun "itertools" từ python 2.6 (và mới hơn) có một số tiện ích có thể trợ giúp ở đó. Một trong số đó là "tee" có thể tạo nhiều bản sao của một trình lặp và lưu vào bộ nhớ cache các kết quả của trình đang chạy phía trước, để các kết quả này được sử dụng trên các bản sao. Tôi sẽ cắt đứt các mục đích của bạn:

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

Đối với DictReader:

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

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

Đối với 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()) trả về tất cả các giá trị còn lại cho trình tạo và đặt lại hiệu quả nếu nó không được lặp lại.


1

Vấn đề

Tôi đã gặp vấn đề tương tự trước đây. Sau khi phân tích mã của mình, tôi nhận ra rằng việc cố gắng đặt lại trình vòng lặp bên trong các vòng lặp làm tăng một chút độ phức tạp về thời gian và nó cũng làm cho mã xấu đi một chút.

Giải pháp

Mở tệp và lưu các hàng vào một biến trong bộ nhớ.

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

Giờ đây, bạn có thể lặp qua các hàng ở bất kỳ đâu trong phạm vi của mình mà không cần xử lý trình lặp.


1

Một tùy chọn khả thi là sử dụng itertools.cycle(), điều này sẽ cho phép bạn lặp lại vô thời hạn mà không cần bất kỳ thủ thuật nào như .seek(0).

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

1

Tôi đang gặp phải vấn đề tương tự - trong khi tôi thích tee()giải pháp, tôi không biết tệp của mình sẽ lớn như thế nào và cảnh báo bộ nhớ về việc tiêu thụ một cái trước khi cái kia khiến tôi không áp dụng phương pháp đó.

Thay vào đó, tôi đang tạo một cặp trình lặp bằng cách sử dụng các iter()câu lệnh và sử dụng trình lặp đầu tiên cho lần chạy đầu tiên của tôi, trước khi chuyển sang trình lặp thứ hai cho lần chạy cuối cùng.

Vì vậy, trong trường hợp của một trình đọc chính tả, nếu trình đọc được xác định bằng cách sử dụng:

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

Tôi có thể tạo một cặp trình lặp từ "đặc tả" này - bằng cách sử dụng:

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

Sau đó, tôi có thể chạy mã vượt qua thứ nhất của mình d1, an toàn khi biết rằng trình lặp thứ hai d2đã được xác định từ cùng một đặc tả gốc.

Tôi chưa thử nghiệm điều này một cách toàn diện, nhưng nó dường như hoạt động với dữ liệu giả.



0

Trả về một trình lặp mới được tạo ở lần lặp cuối cùng trong cuộc gọi '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=' ')

Đầu ra:

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 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.