Puis-je réinitialiser un itérateur/générateur en Python? J'utilise DictReader et souhaite le réinitialiser (à partir du module CSV) au début du fichier.
Je vois beaucoup de réponses suggérant itertools.tee , mais cela ne tient pas compte d’un avertissement crucial dans la documentation:
Cet outil peut nécessiter beaucoup stockage auxiliaire (en fonction de la quantité de données temporaires requises ). En général, si un itérateur utilise la plupart ou la totalité des données avant un autre itérateur commence, c'est plus rapide d'utiliser
list()
au lieu detee()
.
tee
est conçu pour les situations dans lesquelles deux (ou plus) clones d'un même itérateur, tout en "se désynchronisant" l'un l'autre, ne le faites pas beaucoup - plutôt, ils disent dans le même "proximité" (quelques éléments l'un derrière l'autre ou l'un devant l'autre). Ne convient pas au problème de "refaire dès le début" du PO.
L = list(DictReader(...))
, en revanche, convient parfaitement, à condition que la liste des dictionnaires puisse tenir facilement dans la mémoire. Un nouvel "itérateur depuis le début" (très léger et peu onéreux) peut être créé à tout moment avec iter(L)
et utilisé en partie ou en totalité sans affecter les nouveaux ou les existants; d'autres types d'accès sont également facilement disponibles.
Comme plusieurs réponses l'ont fait remarquer à juste titre, dans le cas spécifique de csv
, vous pouvez également .seek(0)
l'objet de fichier sous-jacent (un cas assez particulier). Je ne suis pas sûr que cela soit documenté et garanti, bien que cela fonctionne actuellement; il serait probablement intéressant d’envisager uniquement les fichiers csv vraiment volumineux, dans lesquels list
je recommande, car l’approche générale aurait une empreinte mémoire trop importante.
Si vous avez un fichier csv nommé 'blah.csv' qui ressemble à
a,b,c,d
1,2,3,4
2,3,4,5
3,4,5,6
vous savez que vous pouvez ouvrir le fichier en lecture et créer un DictReader avec
blah = open('blah.csv', 'r')
reader= csv.DictReader(blah)
Ensuite, vous pourrez obtenir la prochaine ligne avec reader.next()
, qui devrait afficher
{'a':1,'b':2,'c':3,'d':4}
l'utiliser à nouveau produira
{'a':2,'b':3,'c':4,'d':5}
Cependant, à ce stade, si vous utilisez blah.seek(0)
, la prochaine fois que vous appelez reader.next()
, vous obtiendrez
{'a':1,'b':2,'c':3,'d':4}
encore.
Cela semble être la fonctionnalité que vous recherchez. Je suis sûr que cette approche comporte certaines astuces que je ne connais toutefois pas. @Brian a suggéré de créer simplement un autre DictReader. Cela ne fonctionnera pas si vous êtes le premier lecteur à mi-chemin de la lecture du fichier, car votre nouveau lecteur aura des clés et des valeurs inattendues, où que vous soyez dans le fichier.
Non. Le protocole d'itérateur de Python est très simple et ne fournit qu'une seule méthode (.next()
ou __next__()
) et aucune méthode pour réinitialiser un itérateur en général.
Le modèle courant consiste à créer un nouvel itérateur à la place.
Si vous voulez "sauver" un itérateur afin de pouvoir en revenir au début, vous pouvez également créer un itérateur à l'aide de itertools.tee
Il y a un bogue dans l'utilisation de .seek (0) comme le préconisent Alex Martelli et Wilduck ci-dessus, à savoir que le prochain appel à .next () vous donnera un dictionnaire de votre ligne d'en-tête sous la forme {key1: key1, key2: , ...}. La solution consiste à suivre file.seek (0) avec un appel à reader.next () pour supprimer la ligne d’en-tête.
Donc, votre code ressemblerait à quelque chose comme ça:
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)
Oui , si vous utilisez numpy.nditer
pour construire votre itérateur.
>>> 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'est peut-être orthogonal à la question initiale, mais on pourrait envelopper l'itérateur dans une fonction qui renvoie l'itérateur.
def get_iter():
return iterator
Pour réinitialiser l'itérateur, rappelez simplement la fonction. Ceci est bien sûr trivial si la fonction lorsque cette fonction ne prend aucun argument.
Si la fonction nécessite des arguments, utilisez functools.partial pour créer une fermeture qui peut être passée à la place de l'itérateur d'origine.
def get_iter(arg1, arg2):
return iterator
from functools import partial
iter_clos = partial(get_iter, a1, a2)
Cela semble éviter la mise en cache que tee (n copies) ou list (1 copie) devrait faire
Bien qu’il n’y ait pas de réinitialisation d’itérateur, le module "itertools" de python 2.6 (et des versions ultérieures) contient des utilitaires qui peuvent y aider. résultats de celui qui précède, de sorte que ces résultats soient utilisés sur les copies. Je vais voir vos objectifs:
>>> 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]
Pour les petits fichiers, vous pouvez envisager d’utiliser more_itertools.seekable
- un outil tiers offrant la possibilité de réinitialiser des itérables.
Démo
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))
Sortie
{'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'}
Ici, une DictReader
est encapsulée dans un objet seekable
(1) et advanced (2). La méthode seek()
est utilisée pour réinitialiser/rembobiner l'itérateur à la position 0 (3).
Remarque: la consommation de mémoire augmente avec les itérations. Soyez donc prudent lorsque vous appliquez cet outil à des fichiers volumineux, comme indiqué dans la documentation .
J'ai eu le même problème avant. Après avoir analysé mon code, je me suis rendu compte que tenter de réinitialiser l'itérateur à l'intérieur des boucles augmentait légèrement la complexité temporelle et rendait le code un peu laid.
Ouvrez le fichier et enregistrez les lignes dans une variable en mémoire.
# 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)
Vous pouvez maintenant parcourir rows n’importe où dans votre champ d’application sans faire appel à un itérateur.
Pour DictReader:
f = open(filename, "rb")
d = csv.DictReader(f, delimiter=",")
f.seek(0)
d.__init__(f, delimiter=",")
Pour 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()
Seulement si le type sous-jacent fournit un mécanisme pour le faire (par exemple, fp.seek(0)
).
L'option possible est d'utiliser itertools.cycle () qui permettra d'itérer indéfiniment sans astuce comme .seek (0)
iterDic = itertools.cycle(csv.DictReader(open('file.csv')))
list(generator())
renvoie toutes les valeurs restantes pour un générateur et le réinitialise efficacement s'il n'est pas mis en boucle.