61 votes

Le moyen le plus simple d'ignorer les lignes vides lors de la lecture d'un fichier en Python

J'ai un code qui lit un fichier de noms et crée une liste :

names_list = open("names", "r").read().splitlines()

Chaque nom est séparé par une nouvelle ligne, comme suit :

Allman
Atkinson

Behlendorf 

Je veux ignorer toutes les lignes qui ne contiennent que des espaces blancs. Je sais que je peux le faire en créant une boucle et en vérifiant chaque ligne que je lis, puis en l'ajoutant à une liste si elle n'est pas vide.

Je me demandais juste s'il y avait une façon plus pythique de le faire ?

2 votes

Il y a une réponse ici : stackoverflow.com/questions/4791080/

84voto

aaronasterling Points 25749

J'empilerais des expressions de générateur :

with open(filename) as f_in:
    lines = (line.rstrip() for line in f_in) # All lines including the blank ones
    lines = (line for line in lines if line) # Non-blank lines

Maintenant, lines est l'ensemble des lignes non vides. Cela vous évitera de devoir appeler deux fois la bande sur la ligne. Si vous voulez une liste de lignes, alors vous pouvez simplement faire :

with open(filename) as f_in:
    lines = (line.rstrip() for line in f_in) 
    lines = list(line for line in lines if line) # Non-blank lines in a list

Vous pouvez également le faire en une seule ligne (à l'exception de with ) mais il n'est pas plus efficace et plus difficile à lire :

with open(filename) as f_in:
    lines = list(line for line in (l.strip() for l in f_in) if line)

Mise à jour :

Je suis d'accord pour dire que c'est laid à cause de la répétition des jetons. Vous pourriez simplement écrire un générateur si vous préférez :

def nonblank_lines(f):
    for l in f:
        line = l.rstrip()
        if line:
            yield line

Alors appelez-le comme ça :

with open(filename) as f_in:
    for line in nonblank_lines(f_in):
        # Stuff

mettre à jour 2 :

with open(filename) as f_in:
    lines = filter(None, (line.rstrip() for line in f_in))

et sur CPython (avec comptage déterministe des références)

lines = filter(None, (line.rstrip() for line in open(filename)))

En Python 2, utilisez itertools.ifilter si vous voulez un générateur et en Python 3, il suffit de passer le tout à list si vous voulez une liste.

0 votes

Je pense que la troisième ligne de votre premier code devrait être la suivante for line in lines .

0 votes

"Cela vous évitera d'avoir à appeler deux fois la bande sur la ligne." - oui ; et c'est certainement plus propre à cet égard ; mais vous finissez toujours par vous répéter, et je me demande si cela compense vraiment, en termes de performances, le fait d'enchaîner les générateurs comme ça. Quelqu'un veut faire des tests ?

0 votes

Karl : Il utilise deux expressions de générateur, mais ce n'est pas une répétition, ce sont des expressions complètement différentes.

22voto

Felix Kling Points 247451

Vous pourriez utiliser la compréhension de liste :

with open("names", "r") as f:
    names_list = [line.strip() for line in f if line.strip()]

Mis à jour : Suppression des éléments inutiles readlines() .

Pour éviter d'appeler line.strip() deux fois, vous pouvez utiliser un générateur :

names_list = [l for l in (line.strip() for line in f) if l]

2 votes

Sympa mais en fait, il appelle strip() deux fois sur chaque ligne.

13voto

Sean Points 3056

Si vous voulez, vous pouvez simplement mettre ce que vous aviez dans une liste de compréhension :

names_list = [line for line in open("names.txt", "r").read().splitlines() if line]

o

all_lines = open("names.txt", "r").read().splitlines()
names_list = [name for name in all_lines if name]

splitlines() a déjà supprimé les fins de ligne.

Je ne pense pas qu'elles soient aussi claires que le simple fait de boucler explicitement :

names_list = []
with open('names.txt', 'r') as _:
    for line in _:
        line = line.strip()
        if line:
            names_list.append(line)

Edit :

Bien que le filtre semble assez lisible et concis :

names_list = filter(None, open("names.txt", "r").read().splitlines())

0 votes

J'ai essayé presque tout ce qui se trouve sur cette page, et votre première ligne a fonctionné parfaitement.

0 votes

Oh merci, j'essayais de faire splitlines().filter plutôt que filter(splitlines())

0 votes

Merci beaucoup, votre réponse est la meilleure pour moi !

10voto

a_r Points 380

Je pense qu'il existe une solution simple que j'ai récemment utilisée après avoir parcouru les nombreuses réponses ici.

with open(file_name) as f_in:   
    for line in f_in:
        if len(line.split()) == 0:
            continue

Cela fait simplement le même travail, en ignorant toutes les lignes vides.

0 votes

C'est peut-être la réponse à la question.

5voto

eyquem Points 9942

Lorsqu'il faut traiter un texte pour en extraire des données, je pense toujours en premier lieu aux regex, car.. :

  • Pour autant que je sache, les regex ont été inventés pour cela.

  • itérer sur les lignes me semble maladroit : cela consiste essentiellement à chercher les nouvelles lignes puis à chercher les données à extraire dans chaque ligne ; cela fait deux recherches au lieu d'une seule directe avec une regex

  • La façon d'utiliser les regex est facile ; seule l'écriture d'une chaîne de regex à compiler dans un objet regex est parfois difficile, mais dans ce cas, le traitement avec une itération sur les lignes sera également compliqué.

Pour le problème discuté ici, une solution regex est rapide et facile à écrire :

import re
names = re.findall('\S+',open(filename).read())

J'ai comparé les vitesses de plusieurs solutions :

import re
from time import clock

A,AA,B1,B2,BS,reg = [],[],[],[],[],[]
D,Dsh,C1,C2 = [],[],[],[]
F1,F2,F3  = [],[],[]

def nonblank_lines(f):
    for l in f:
        line = l.rstrip()
        if line:  yield line

def short_nonblank_lines(f):
    for l in f:
        line = l[0:-1]
        if line:  yield line

for essays in xrange(50):

    te = clock()
    with open('raa.txt') as f:
        names_listA = [line.strip() for line in f if line.strip()] # Felix Kling
    A.append(clock()-te)

    te = clock()
    with open('raa.txt') as f:
        names_listAA = [line[0:-1] for line in f if line[0:-1]] # Felix Kling with line[0:-1]
    AA.append(clock()-te)

    #-------------------------------------------------------
    te = clock()
    with open('raa.txt') as f_in:
        namesB1 = [ name for name in (l.strip() for l in f_in) if name ] # aaronasterling without list()
    B1.append(clock()-te)

    te = clock()
    with open('raa.txt') as f_in:
        namesB2 = [ name for name in (l[0:-1] for l in f_in) if name ] # aaronasterling without list() and with line[0:-1]
    B2.append(clock()-te)

    te = clock()
    with open('raa.txt') as f_in:
        namesBS = [ name for name in f_in.read().splitlines() if name ] # a list comprehension with read().splitlines()
    BS.append(clock()-te)

    #-------------------------------------------------------
    te = clock()
    with open('raa.txt') as f:
        xreg = re.findall('\S+',f.read()) #  eyquem
    reg.append(clock()-te)

    #-------------------------------------------------------
    te = clock()
    with open('raa.txt') as f_in:
        linesC1 = list(line for line in (l.strip() for l in f_in) if line) # aaronasterling
    C1.append(clock()-te)

    te = clock()
    with open('raa.txt') as f_in:
        linesC2 = list(line for line in (l[0:-1] for l in f_in) if line) # aaronasterling  with line[0:-1]
    C2.append(clock()-te)

    #-------------------------------------------------------
    te = clock()
    with open('raa.txt') as f_in:
        yD = [ line for line in nonblank_lines(f_in)  ] # aaronasterling  update
    D.append(clock()-te)

    te = clock()
    with open('raa.txt') as f_in:
        yDsh = [ name for name in short_nonblank_lines(f_in)  ] # nonblank_lines with line[0:-1]
    Dsh.append(clock()-te)

    #-------------------------------------------------------
    te = clock()
    with open('raa.txt') as f_in:
        linesF1 = filter(None, (line.rstrip() for line in f_in)) # aaronasterling update 2
    F1.append(clock()-te)

    te = clock()
    with open('raa.txt') as f_in:
        linesF2 = filter(None, (line[0:-1] for line in f_in)) # aaronasterling update 2 with line[0:-1]
    F2.append(clock()-te)

    te = clock()
    with open('raa.txt') as f_in:
        linesF3 =  filter(None, f_in.read().splitlines()) # aaronasterling update 2 with read().splitlines()
    F3.append(clock()-te)

print 'names_listA == names_listAA==namesB1==namesB2==namesBS==xreg\n  is ',\
       names_listA == names_listAA==namesB1==namesB2==namesBS==xreg
print 'names_listA == yD==yDsh==linesC1==linesC2==linesF1==linesF2==linesF3\n  is ',\
       names_listA == yD==yDsh==linesC1==linesC2==linesF1==linesF2==linesF3,'\n\n\n'

def displ((fr,it,what)):  print fr + str( min(it) )[0:7] + '   ' + what

map(displ,(('* ', A,    '[line.strip() for line in f if line.strip()]               * Felix Kling\n'),

           ('  ', B1,   '    [name for name in (l.strip() for l in f_in) if name ]    aaronasterling without list()'),
           ('* ', C1,   'list(line for line in (l.strip() for l in f_in) if line)   * aaronasterling\n'),          

           ('* ', reg,  're.findall("\S+",f.read())                                 * eyquem\n'),

           ('* ', D,    '[ line for line in       nonblank_lines(f_in)  ]           * aaronasterling  update'),
           ('  ', Dsh,  '[ line for line in short_nonblank_lines(f_in)  ]             nonblank_lines with line[0:-1]\n'),

           ('* ', F1 ,  'filter(None, (line.rstrip() for line in f_in))             * aaronasterling update 2\n'),

           ('  ', B2,   '    [name for name in (l[0:-1]   for l in f_in) if name ]    aaronasterling without list() and with line[0:-1]'),
           ('  ', C2,   'list(line for line in (l[0:-1]   for l in f_in) if line)     aaronasterling  with line[0:-1]\n'),

           ('  ', AA,   '[line[0:-1] for line in f if line[0:-1]  ]                   Felix Kling with line[0:-1]\n'),

           ('  ', BS,   '[name for name in f_in.read().splitlines() if name ]        a list comprehension with read().splitlines()\n'),

           ('  ', F2 ,  'filter(None, (line[0:-1] for line in f_in))                  aaronasterling update 2 with line[0:-1]'),

           ('  ', F3 ,  'filter(None, f_in.read().splitlines()                        aaronasterling update 2 with read().splitlines()'))
    )

La solution avec regex est simple et nette. Cependant, elle n'est pas parmi les plus rapides. La solution d'aaronasterling avec filter() est étonnamment rapide pour moi (je ne connaissais pas la vitesse de ce filter() particulier) et les temps des solutions optimisées descendent jusqu'à 27 % du plus grand temps. Je me demande ce qui fait le miracle de l'association filtre-splitlines :

names_listA == names_listAA==namesB1==namesB2==namesBS==xreg
  is  True
names_listA == yD==yDsh==linesC1==linesC2==linesF1==linesF2==linesF3
  is  True 

* 0.08266   [line.strip() for line in f if line.strip()]               * Felix Kling

  0.07535       [name for name in (l.strip() for l in f_in) if name ]    aaronasterling without list()
* 0.06912   list(line for line in (l.strip() for l in f_in) if line)   * aaronasterling

* 0.06612   re.findall("\S+",f.read())                                 * eyquem

* 0.06486   [ line for line in       nonblank_lines(f_in)  ]           * aaronasterling  update
  0.05264   [ line for line in short_nonblank_lines(f_in)  ]             nonblank_lines with line[0:-1]

* 0.05451   filter(None, (line.rstrip() for line in f_in))             * aaronasterling update 2

  0.04689       [name for name in (l[0:-1]   for l in f_in) if name ]    aaronasterling without list() and with line[0:-1]
  0.04582   list(line for line in (l[0:-1]   for l in f_in) if line)     aaronasterling  with line[0:-1]

  0.04171   [line[0:-1] for line in f if line[0:-1]  ]                   Felix Kling with line[0:-1]

  0.03265   [name for name in f_in.read().splitlines() if name ]        a list comprehension with read().splitlines()

  0.03638   filter(None, (line[0:-1] for line in f_in))                  aaronasterling update 2 with line[0:-1]
  0.02198   filter(None, f_in.read().splitlines()                        aaronasterling update 2 with read().splitlines()

Mais ce problème est particulier, le plus simple de tous : un seul nom par ligne. Les solutions ne sont donc que des jeux avec des lignes, des scissions et des coupes [0:-1].

Au contraire, la regex n'a pas d'importance avec les lignes, elle trouve directement les données souhaitées : Je considère qu'il s'agit d'une méthode de résolution plus naturelle, qui s'applique des cas les plus simples aux plus complexes, et qui est donc souvent la méthode à privilégier dans les traitements de textes.

EDIT

J'ai oublié de dire que j'utilise Python 2.7 et que j'ai mesuré les temps ci-dessus avec un fichier contenant 500 fois la chaîne suivante

SMITH
JONES
WILLIAMS
TAYLOR
BROWN
DAVIES
EVANS
WILSON
THOMAS
JOHNSON

ROBERTS
ROBINSON
THOMPSON
WRIGHT
WALKER
WHITE
EDWARDS
HUGHES
GREEN
HALL

LEWIS
HARRIS
CLARKE
PATEL
JACKSON
WOOD
TURNER
MARTIN
COOPER
HILL

WARD
MORRIS
MOORE
CLARK
LEE
KING
BAKER
HARRISON
MORGAN
ALLEN

JAMES
SCOTT
PHILLIPS
WATSON
DAVIS
PARKER
PRICE
BENNETT
YOUNG
GRIFFITHS

MITCHELL
KELLY
COOK
CARTER
RICHARDSON
BAILEY
COLLINS
BELL
SHAW
MURPHY

MILLER
COX
RICHARDS
KHAN
MARSHALL
ANDERSON
SIMPSON
ELLIS
ADAMS
SINGH

BEGUM
WILKINSON
FOSTER
CHAPMAN
POWELL
WEBB
ROGERS
GRAY
MASON
ALI

HUNT
HUSSAIN
CAMPBELL
MATTHEWS
OWEN
PALMER
HOLMES
MILLS
BARNES
KNIGHT

LLOYD
BUTLER
RUSSELL
BARKER
FISHER
STEVENS
JENKINS
MURRAY
DIXON
HARVEY

0 votes

Quelques points. On n'écrirait jamais [line for line in generator()] on pourrait simplement écrire list(generator()) . Essayez de travailler avec les builtins quand c'est possible. Ils sont écrits en C et tout le monde sait ce qu'ils font. Aussi, j'appelais str.rstrip no str.split . Je ne sais pas s'il y aura un gain de performance. Enfin, filter(None, ....) est si rapide parce qu'il encapsule toute la logique en C.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X