Comment lire un fichier ligne par ligne en Python?

137

À l'époque préhistorique (Python 1.4), nous avons fait:

fp = open('filename.txt')
while 1:
    line = fp.readline()
    if not line:
        break
    print line

après Python 2.1, nous avons fait:

for line in open('filename.txt').xreadlines():
    print line

avant d'avoir le protocole d'itérateur pratique dans Python 2.3, et pourrions faire:

for line in open('filename.txt'):
    print line

J'ai vu quelques exemples utilisant les plus verbeux:

with open('filename.txt') as fp:
    for line in fp:
        print line

est-ce la méthode préférée pour l'avenir?

[modifier] J'obtiens que l'instruction with assure la fermeture du fichier ... mais pourquoi n'est-ce pas inclus dans le protocole d'itérateur pour les objets fichier?

thebjorn
la source
4
à mon humble avis, la dernière suggestion n'est pas plus verbeuse que la précédente. Cela fait juste plus de travail (garantit que le fichier est fermé lorsque vous avez terminé).
azhrei
1
@azhrei c'est une ligne de plus, donc objectivement c'est plus verbeux.
thebjorn
7
Je comprends ce que vous dites, mais je dis simplement que comparer des pommes avec des pommes, l'avant-dernière suggestion de votre article nécessite également un code de gestion des exceptions pour correspondre à ce que fait la dernière option. Donc, dans la pratique, c'est plus verbeux. Je suppose que cela dépend du contexte, laquelle des deux dernières options est vraiment la meilleure.
azhrei

Réponses:

227

Il y a exactement une raison pour laquelle ce qui suit est préféré:

with open('filename.txt') as fp:
    for line in fp:
        print line

Nous sommes tous gâtés par le schéma de comptage de références relativement déterministe de CPython pour le garbage collection. D'autres implémentations hypothétiques de Python ne fermeront pas nécessairement le fichier "assez rapidement" sans le withbloc si elles utilisent un autre schéma pour récupérer la mémoire.

Dans une telle implémentation, vous pouvez obtenir une erreur "trop ​​de fichiers ouverts" du système d'exploitation si votre code ouvre les fichiers plus rapidement que le garbage collector appelle les finaliseurs sur les descripteurs de fichiers orphelins. La solution habituelle de contournement consiste à déclencher le GC immédiatement, mais c'est un mauvais hack et cela doit être fait par chaque fonction qui pourrait rencontrer l'erreur, y compris celles des bibliothèques. Quel cauchemard.

Ou vous pouvez simplement utiliser le withbloc.

Question bonus

(Arrêtez de lire maintenant si vous n'êtes intéressé que par les aspects objectifs de la question.)

Pourquoi cela n'est-il pas inclus dans le protocole d'itérateur pour les objets fichier?

C'est une question subjective sur la conception d'API, j'ai donc une réponse subjective en deux parties.

Au niveau de l'intestin, cela semble faux, car cela oblige le protocole de l'itérateur à faire deux choses distinctes - itérer sur les lignes et fermer le descripteur de fichier - et c'est souvent une mauvaise idée de faire faire deux actions à une fonction simple. Dans ce cas, cela se sent particulièrement mal car les itérateurs sont liés de manière quasi fonctionnelle et basée sur la valeur au contenu d'un fichier, mais la gestion des descripteurs de fichiers est une tâche complètement distincte. Ecraser les deux, de manière invisible, en une seule action est surprenant pour les humains qui lisent le code et rend plus difficile le raisonnement sur le comportement du programme.

D'autres langues sont essentiellement parvenues à la même conclusion. Haskell a brièvement flirté avec le soi-disant «paresseux IO» qui vous permet d'itérer sur un fichier et de le fermer automatiquement lorsque vous arrivez à la fin du flux, mais il est presque universellement découragé d'utiliser des IO paresseux dans Haskell ces jours-ci, et Haskell les utilisateurs sont généralement passés à une gestion des ressources plus explicite comme Conduit qui se comporte plus comme le withbloc en Python.

Sur le plan technique, il y a certaines choses que vous voudrez peut-être faire avec un descripteur de fichier en Python qui ne fonctionneraient pas aussi bien si l'itération fermait le descripteur de fichier. Par exemple, supposons que je doive parcourir le fichier deux fois:

with open('filename.txt') as fp:
    for line in fp:
        ...
    fp.seek(0)
    for line in fp:
        ...

Bien que ce soit un cas d'utilisation moins courant, considérez le fait que j'aurais pu simplement ajouter les trois lignes de code en bas à une base de code existante qui avait à l'origine les trois lignes supérieures. Si l'itération fermait le fichier, je ne pourrais pas le faire. Ainsi, séparer les itérations et la gestion des ressources permet de composer plus facilement des morceaux de code dans un programme Python plus volumineux et fonctionnel.

La composabilité est l'une des fonctionnalités d'utilisabilité les plus importantes d'un langage ou d'une API.

Dietrich Epp
la source
1
+1 parce qu'il explique le "quand" dans mon commentaire sur l'op ;-)
azhrei
même avec une implémentation alternative, le gestionnaire with ne vous causera des problèmes que pour les programmes qui ouvrent des centaines de fichiers dans des successions très rapides. La plupart des programmes peuvent se débrouiller sans problème avec la référence de fichier suspendue. À moins que vous ne le désactiviez, le GC se lancera un jour et effacera le descripteur de fichier. withvous donne la tranquillité d'esprit, c'est donc toujours une bonne pratique.
Lie Ryan
1
@DietrichEpp: peut-être que «référence de fichier en suspens» n'était pas les bons mots, je voulais vraiment dire des descripteurs de fichiers qui n'étaient plus accessibles mais pas encore fermés. Dans tous les cas, le GC fermera le descripteur de fichier lorsqu'il collectera l'objet fichier, donc tant que vous n'avez pas de références supplémentaires à l'objet fichier et que vous ne désactivez pas GC et que vous n'ouvrez pas beaucoup de fichiers rapidement succession, il est peu probable que vous obteniez «trop de fichiers ouverts» en raison de la non-fermeture du fichier.
Lie Ryan
1
Oui, c'est exactement ce que j'entends par "si votre code ouvre les fichiers plus rapidement que le garbage collector appelle les finaliseurs sur les descripteurs de fichiers orphelins".
Dietrich Epp
1
La principale raison d'utiliser avec est que si vous ne fermez pas le fichier, il ne sera pas nécessairement écrit immédiatement.
Antimoine
20

Oui,

with open('filename.txt') as fp:
    for line in fp:
        print line

est la voie à suivre.

Ce n'est pas plus verbeux. C'est plus sûr.

eumiro
la source
5

si vous êtes désactivé par la ligne supplémentaire, vous pouvez utiliser une fonction wrapper comme ceci:

def with_iter(iterable):
    with iterable as iter:
        for item in iter:
            yield item

for line in with_iter(open('...')):
    ...

dans Python 3.3, l' yield frominstruction rendrait cela encore plus court:

def with_iter(iterable):
    with iterable as iter:
        yield from iter
Mensonge Ryan
la source
2
appelez la fonction xreadlines .. et placez-le dans un fichier nommé xreadlines.py et nous revenons à la syntaxe Python 2.1 :-)
thebjorn
@thebjorn: peut-être, mais l'exemple Python 2.1 que vous avez cité n'était pas à l'abri d'un gestionnaire de fichiers non fermé dans d'autres implémentations. Une lecture de fichier Python 2.1 à l'abri du gestionnaire de fichiers non fermé prendrait au moins 5 lignes.
Lie Ryan
-1
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()
Rekaut
la source
5
Cela ne répond pas vraiment à la question
Thayne