web-dev-qa-db-fra.com

Comment lire un fichier ligne par ligne en Python?

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

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

après Python 2.1, nous avons:

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

avant que nous ayons le protocole pratique d'itérateur dans Python 2.3, et que nous puissions faire:

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

J'ai vu des exemples utilisant le plus verbeux:

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

est-ce la méthode préférée pour aller de l'avant?

[edit] Je comprends que l'instruction with assure la fermeture du fichier ... mais pourquoi n'est-elle pas incluse dans le protocole itérateur pour les objets fichier?

127
thebjorn

Il y a exactement une raison pour laquelle on préfère:

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 pour la récupération de place de CPython. D'autres implémentations hypothétiques de Python ne fermeront pas nécessairement le fichier assez rapidement sans le bloc with si elles utilisent un autre schéma pour récupérer de la mémoire.

Dans une telle implémentation, le système d’exploitation peut générer l’erreur "Trop de fichiers ouverts" si votre code ouvre les fichiers plus rapidement que le garbage collector n’appelle les finaliseurs sur les descripteurs de fichiers orphelins. La solution habituelle consiste à déclencher le CPG immédiatement, mais il s’agit d’un mauvais bidouillage qui doit être effectué par la fonction chaque qui pourrait rencontrer l’erreur, y compris celles des bibliothèques. Quel cauchemard.

Ou vous pouvez simplement utiliser le bloc with.

Question bonus

(Arrêtez de lire maintenant si vous ne vous intéressez qu'aux aspects objectifs de la question.)

Pourquoi n'est-ce pas inclus dans le protocole itérateur pour les objets de fichier?

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

Cela semble inacceptable au niveau intestinal, car le protocole itérateur a deux fonctions distinctes: itérer sur des lignes et fermer le descripteur de fichier. mauvaise idée de faire une simple fonction faire deux actions. Dans ce cas, cela semble particulièrement désagréable, car les itérateurs se rapportent de manière quasi fonctionnelle au contenu d'un fichier, mais la gestion des descripteurs de fichier est une tâche complètement distincte. Réduire les deux, de manière invisible, en une seule action est surprenant pour les humains qui lisent le code et rend plus difficile la raisonnement sur le comportement du programme.

Les autres langues sont essentiellement arrivées à la même conclusion. Haskell a brièvement flirté avec ce qu'on appelle "l'E/S paresseux" qui vous permet de parcourir un fichier et de le fermer automatiquement lorsque vous arrivez à la fin du flux, mais il est presque universellement déconseillé d'utiliser le paresseux IO in Haskell ces jours-ci, et les utilisateurs de Haskell ont généralement opté pour une gestion plus explicite des ressources, comme Conduit, qui se comporte davantage comme le bloc with en Python.

Sur le plan technique, vous pouvez souhaiter effectuer certaines opérations avec un descripteur de fichier dans Python, ce qui ne fonctionnerait pas aussi bien si l'itération fermait le descripteur de fichier. Par exemple, supposons que je doive parcourir deux fois le fichier:

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 je viens peut-être d'ajouter les trois lignes de code du bas à une base de code existante qui comportait à l'origine les trois premières lignes. Si itération fermait le fichier, je ne pourrais pas le faire. En séparant les itérations et la gestion des ressources, il est ainsi plus facile de composer des fragments de code dans un programme de travail plus grand, Python.

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

215
Dietrich Epp

Oui,

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

est le chemin à parcourir.

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

19
eumiro

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 Python3.3, l'instruction yield from rendrait cela encore plus court:

def with_iter(iterable):
    with iterable as iter:
        yield from iter
3
Lie Ryan