web-dev-qa-db-fra.com

Les itérateurs peuvent-ils être réinitialisés en Python?

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.

101
user248237dfsf

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 de tee().

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.

67
Alex Martelli

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.

29
Wilduck

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

21
u0b34a0f6ae

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)
10
Steven Rumbalski

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

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

3
Anish

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]
2
jsbueno

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 .

1
pylang

Problème

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.

Solution

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.

1
Anthony Holloman

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()
0
mAsT3RpEE

Seulement si le type sous-jacent fournit un mécanisme pour le faire (par exemple, fp.seek(0)).

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')))
0
Greg H

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.

0
Theoremiser