Yineleyiciler Python'da sıfırlanabilir mi?


130

Python'da bir yineleyiciyi / oluşturucuyu sıfırlayabilir miyim? DictReader kullanıyorum ve onu dosyanın başlangıcına sıfırlamak istiyorum.



Bir yan notta, list()fonksiyonun argümanını (yinelenebilir) yinelediğini buldum. Böylece list()aynı yinelemeyi iki kez aradığınızda (örneğin sonucu zip()), ikinci aramada boş bir liste alırsınız!
theaws.blog

Yanıtlar:


84

İtertools.tee'yi öneren birçok yanıt görüyorum , ancak bu, belgelerde bunun için önemli bir uyarıyı görmezden geliyor:

Bu itertool, önemli miktarda yardımcı depolama gerektirebilir (ne kadar geçici verinin depolanması gerektiğine bağlı olarak). Genel olarak, bir yineleyici, başka bir yineleyici başlamadan önce verilerin çoğunu veya tamamını kullanırsa, bunun list()yerine kullanımı daha hızlıdır tee().

Temel olarak, teebir yineleyicinin iki (veya daha fazla) klonunun birbirleriyle "senkronizasyondan çıkarken" bunu çok fazla yapmadığı durumlar için tasarlanmıştır - daha ziyade aynı "çevrede" dedikleri birbirinin arkasında veya önünde birkaç öğe). OP'nin "baştan itibaren yeniden yap" problemi için uygun değildir.

L = list(DictReader(...))Öte yandan, sözler listesi hafızaya rahatça sığabildiği sürece mükemmel bir şekilde uygundur. Yeni bir "baştan itibaren yineleyici" (çok hafif ve düşük maliyetli) herhangi bir zamanda yapılabilir ve iter(L)yenilerini veya mevcutları etkilemeden kısmen veya tamamen kullanılabilir; diğer erişim modelleri de kolayca elde edilebilir.

Birkaç yanıtın haklı olarak belirtildiği gibi, belirli bir durumda temel dosya nesnesini csvde yapabilirsiniz .seek(0)(oldukça özel bir durum). Bunun belgelendiğinden ve garantili olduğundan emin değilim, ancak şu anda çalışıyor; Muhtemelen yalnızca list, genel yaklaşımın çok büyük bir bellek ayak izine sahip olacağını önerdiğim gerçekten büyük csv dosyaları için düşünmeye değer .


6
list()5MB'lik bir dosyada bir csvreader üzerinden çokluassage'ı önbelleğe almak için kullanmak , çalışma zamanımın ~ 12sn'den ~ 0.5s'ye gittiğini görüyor.
John Mee

33

"Blah.csv" adlı bir csv dosyanız varsa

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

dosyayı okumak için açabileceğinizi ve bir DictReader oluşturabileceğinizi biliyorsunuz.

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

Ardından, bir sonraki satırın reader.next()çıktısını alabileceksiniz.

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

tekrar kullanmak üretecek

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

Ancak, kullanmak eğer bu noktada blah.seek(0), aramak dahaki sefere reader.next()seni alacak

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

tekrar.

Bu, aradığınız işlevsellik gibi görünüyor. Eminim ki bu yaklaşımla ilgili farkında olmadığım bazı püf noktaları vardır. @Brian, başka bir DictReader oluşturmayı önerdi. Yeni okuyucunuz dosyanın neresinde olursanız olun beklenmedik anahtarlara ve değerlere sahip olacağından, ilk okuyucunuz dosyayı okumanın yarısındaysa bu işe yaramaz.


Teorimin bana söylediği buydu, olması gerektiğini düşündüğüm şeyin işe yaradığını görmek güzel.
Wayne Werner

@Wilduck: DictReader'ın başka bir örneğiyle tanımladığınız davranış, yeni bir dosya tanıtıcısı yapıp bunu ikinci DictReader'a iletirseniz gerçekleşmez, değil mi?

İki dosya işleyiciniz varsa, bağımsız olarak davranacaklardır, evet.
Wilduck

24

Hayır. Python'un yineleyici protokolü çok basittir ve yalnızca tek bir yöntem ( .next()veya __next__()) sağlar ve genel olarak bir yineleyiciyi sıfırlama yöntemi yoktur.

Yaygın model, bunun yerine aynı prosedürü kullanarak yeni bir yineleyici oluşturmaktır.

Bir yineleyiciyi, başlangıcına geri dönebilmeniz için "kurtarmak" istiyorsanız, yineleyiciyi kullanarak da çatallayabilirsiniz. itertools.tee


1
.Next () yönteminin analizi muhtemelen doğru olsa da, operasyonun istediğini elde etmenin oldukça basit bir yolu var.
Wilduck

2
@Wilduck: Cevabını görüyorum. Yineleyici sorusunu az önce cevapladım ve csvmodül hakkında hiçbir fikrim yok . Umarım her iki yanıt da orijinal poster için yararlıdır.
u0b34a0f6ae

Yineleyici protokolü de kesinlikle gerektirir __iter__. Yani yineleyiciler de yinelenebilir olmalıdır.
Steve Jessop

11

Evet , numpy.nditeryineleyicinizi oluşturmak için kullanırsanız .

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

Can nditergibi dizi aracılığıyla döngü itertools.cycle?
LWZ

1
@LWZ: Ben öyle düşünmüyorum, ama olabilir ve bir üzerinde istisnasız bir yapın . try:next()StopIterationreset()
sonraki duyuruya kadar duraklatıldı.


Aradığım buydu!
sriram

1
Buradaki "işlenenler" sınırının 32 olduğunu unutmayın: stackoverflow.com/questions/51856685/…
Simon

11

.seek(0)Yukarıda Alex Martelli ve Wilduck tarafından savunulduğu gibi kullanımda bir hata var , yani bir sonraki çağrı .next()size başlık satırınızın şeklinde bir sözlük verecektir {key1:key1, key2:key2, ...}. Çözüm , başlık satırından kurtulmak için file.seek(0)bir çağrı ile takip reader.next()etmektir.

Yani kodunuz şuna benzer:

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

Bu, belki de orijinal soruya diktir, ancak yineleyici, yineleyiciyi döndüren bir fonksiyona sarılabilir.

def get_iter():
    return iterator

Yineleyiciyi sıfırlamak için işlevi yeniden çağırmanız yeterlidir. Söz konusu işlev hiçbir argüman almadığında işlev elbette önemsizdir.

İşlevin bazı bağımsız değişkenler gerektirmesi durumunda, orijinal yineleyici yerine geçirilebilecek bir kapanış oluşturmak için functools.partial kullanın.

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

Bu, tişörtün (n kopya) veya listenin (1 kopya) yapması gereken önbelleğe almayı önler


3

Küçük dosyalar için, more_itertools.seekableyinelemelerin sıfırlanmasını sağlayan üçüncü taraf bir araç kullanmayı düşünebilirsiniz .

gösteri

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

Çıktı

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

Burada a DictReader, bir seekablenesneye (1) ve gelişmiş (2) ' ye sarılır . seek()Yöntem / yeniden 0. konumuna (3) için bir yineleyici sarmak için kullanılmaktadır.

Not: bellek tüketimi yinelemeyle artar, bu nedenle bu aracı belgelerde belirtildiği gibi büyük dosyalara uygularken dikkatli olun .


2

Yineleyici sıfırlaması olmasa da, python 2.6'dan (ve sonraki sürümlerden) "itertools" modülünde yardımcı olabilecek bazı yardımcı programlar vardır. Bunlardan biri, bir yineleyicinin birden çok kopyasını oluşturabilen ve ileride olanın sonuçlarını önbelleğe alabilen, böylece bu sonuçların kopyalarda kullanılmasını sağlayan "tee" dir. Amaçlarınızı seveceğim:

>>> 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 için:

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

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

DictWriter için:

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()) bir jeneratör için kalan tüm değerleri döndürür ve döngü halinde değilse onu etkin bir şekilde sıfırlar.


1

Sorun

Daha önce de aynı sorunu yaşadım. Kodumu analiz ettikten sonra, döngülerin içindeki yineleyiciyi sıfırlamaya çalışmanın zaman karmaşıklığını biraz artırdığını ve aynı zamanda kodu biraz çirkinleştirdiğini fark ettim.

Çözüm

Dosyayı açın ve satırları bellekteki bir değişkene kaydedin.

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

Artık bir yineleyici ile uğraşmadan kapsamınızın herhangi bir yerinde satırlar arasında döngü yapabilirsiniz .


1

Olası bir seçenek, kullanmaktır itertools.cycle(), bu, herhangi bir numara olmadan süresiz olarak yineleme yapmanıza izin verir .seek(0).

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

1

Aynı konuya geliyorum - hoşuma giderken tee() çözümü , dosyalarımın ne kadar büyük olacağını bilmiyorum ve birini diğerinden önce tüketmeyle ilgili hafıza uyarıları beni bu yöntemi benimsemekten alıkoyuyor.

Bunun yerine, kullanarak bir çift yineleyici oluşturuyorum iter() son çalıştırma için ikinciye geçmeden önce, ifadeleri kullanarak ve ilk çalıştırmam için .

Öyleyse, bir dikt okuyucu durumunda, okuyucu şu şekilde tanımlanmışsa:

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

Bu "spesifikasyondan" bir çift yineleyici oluşturabilirim - şunu kullanarak:

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

Daha sonra d1, ikinci yineleyicinin d2aynı kök spesifikasyonundan tanımlandığını bilerek 1. geçiş kodumu çalıştırabilirim .

Bunu kapsamlı bir şekilde test etmedim, ancak sahte verilerle çalışıyor gibi görünüyor.


1

'İter ()' çağrısı sırasında son yinelemede yeni oluşturulmuş bir yineleyici döndür

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

Çıktı:

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 

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.