【问题标题】:Easiest way to ignore blank lines when reading a file in Python在 Python 中读取文件时忽略空行的最简单方法
【发布时间】:2011-01-30 09:05:20
【问题描述】:

我有一些代码可以读取名称文件并创建一个列表:

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

每个名称用换行符分隔,如下所示:

Allman
Atkinson

Behlendorf 

我想忽略任何只包含空格的行。我知道我可以通过创建一个循环并检查我阅读的每一行然后将其添加到列表中(如果它不为空白)来做到这一点。

我只是想知道是否有更 Pythonic 的方式来做这件事?

【问题讨论】:

标签: python


【解决方案1】:

我会堆叠生成器表达式:

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

现在,lines 是所有非空行。这将使您不必在线路上调用 strip 两次。如果你想要一个行列表,那么你可以这样做:

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

您也可以在单行中执行此操作(不包括 with 语句),但它并不高效且更难阅读:

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

更新:

我同意这很丑,因为令牌的重复。如果你愿意,你可以只写一个生成器:

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

然后这样称呼它:

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

更新 2:

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

在 CPython 上(使用确定性引用计数)

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

在 Python 2 中,如果您想要一个生成器,请使用 itertools.ifilter;在 Python 3 中,如果您想要一个列表,只需将整个内容传递给 list

【讨论】:

  • 我认为您的第一个代码中的第三行应该是for line in lines
  • "这将使您不必在线路上调用 strip 两次。" - 是的;在这方面它肯定更整洁;但你最终还是会重复自己,我不得不怀疑它是否真的在性能方面弥补了像这样的链接生成器的开销。有人想做一些测试吗?
  • @Karl:他使用了两个生成器表达式,但这不是重复——它们是完全不同的表达式。
  • @aaronsterling:你的第一个单行者很难读懂,以至于你和快乐的投票者和评论者都没有注意到它在l.strip 之后需要() :-)
  • +1 表示nonblank_lines 函数。这应该是第一。其余的要么是代码高尔夫,要么是内存猪,因为它将整个文件读入单个列表,其中(有时)不需要整个文件。
【解决方案2】:

如果你愿意,你可以把你拥有的东西放在一个列表理解中:

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

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

splitlines() 已经删除了行尾。

我认为这些不如明确循环那样清晰:

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

编辑:

虽然,过滤器看起来非常可读和简洁:

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

【讨论】:

  • 我已经尝试了这个页面上的几乎所有东西,你的第一行效果很好。
  • 您可以使用.readlines() 而不是.read().splitlines()。当然,两者都不如使用生成器/循环。
  • 我相信 readlines() 不会摆脱 '\n'
【解决方案3】:

我想有一个简单的解决方案,我在这里经历了这么多答案后最近使用了它。

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

这只是做同样的工作,忽略所有空行。

【讨论】:

    【解决方案4】:

    为什么你们都在努力?

    with open("myfile") as myfile:
        nonempty = filter(str.rstrip, myfile)
    

    如果您有这样做的冲动,请将 nonempty 转换为列表,尽管我强烈建议将 nonempty 保留为 Python 3.x 中的生成器

    在 Python 2.x 中,您可以使用 itertools.ifilter 代替您的出价。

    【讨论】:

      【解决方案5】:

      您可以在 Python >= 3.8 中使用 Walrus 运算符

      with open('my_file') as fd:
          nonblank = [stripped for line in fd if (stripped := line.strip())]
      

      认为'blablabla 如果剥离(定义为 line.strip)是真实的'

      【讨论】:

        【解决方案6】:

        您可以使用列表推导:

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

        更新:删除了不必要的readlines()

        为避免两次调用line.strip(),您可以使用生成器:

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

        【讨论】:

        • 不错,但实际上每行调用了两次 strip()。
        【解决方案7】:

        你可以使用not:

        for line in lines:
            if not line:
                continue
        

        【讨论】:

          【解决方案8】:

          当必须对文本进行处理以从中提取数据时,我总是首先想到正则表达式,因为:

          • 据我所知,已经为此发明了正则表达式

          • 迭代行对我来说似乎很笨拙:它基本上包括搜索换行符,然后搜索要在每一行中提取的数据;使用正则表达式进行两次搜索而不是直接唯一的一次搜索

          • 使用正则表达式的方法很简单;只有将正则表达式字符串编写成正则表达式对象有时会很困难,但在这种情况下,对行进行迭代的处理也会很复杂

          对于这里讨论的问题,正则表达式解决方案快速且易于编写:

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

          我比较了几种解决方案的速度:

          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()'))
              )
          

          使用正则表达式的解决方案简单明了。虽然,它不是最快的。 使用 filter() 的 aaronasterling 的解决方案对我来说非常快(我不知道这个特定的 filter() 的速度),优化解决方案的时间下降到最大时间的 27%。我想知道是什么让 filter-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()
          

          但是这个问题是特殊的,也是最简单的:每一行只有一个名字。所以解决方案只是带有线条、分割和 [0:-1] 切割的游戏。

          相反,正则表达式与行无关,它直接找到所需的数据:我认为这是一种更自然的解析方式,适用于从最简单的情况到更复杂的情况,因此通常是文本处理中优先考虑。

          编辑

          我忘了说我使用的是 Python 2.7,我用一个包含 500 倍以下链的文件测量了上述时间

          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
          

          【讨论】:

          • 几点。一个永远不会写[line for line in generator()],一个只会写list(generator())。尽可能尝试使用内置函数。它们是用 C 语言编写的,每个人都知道它们在做什么。另外,我打电话给str.rstrip 而不是str.split。不知道会不会有性能提升。最后,filter(None, ....) 之所以如此之快,是因为它将所有逻辑封装在 C 中。
          【解决方案9】:

          @S.Lott

          以下代码一次处理一行,并产生一个不急于内存的结果:

          filename = 'english names.txt'
          
          with open(filename) as f_in:
              lines = (line.rstrip() for line in f_in)
              lines = (line for line in lines if line)
              the_strange_sum = 0
              for l in lines:
                  the_strange_sum += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.find(l[0])
          
          print the_strange_sum
          

          所以生成器 (line.rstrip() for line in f_in) 与 nonblank_lines() 函数完全一样可以接受。

          【讨论】:

          • @S.Lott 没有。如果我使用打印类型(行)进行测试,我会获得 。所以行不是一个包含数据的对象,它的大小很小或很大。而 lines = (line.rstrip() for line in f_in) 不会将任何东西单独放在内存中,它提供了迭代的可能性。我上面的代码并没有将整个文件放入内存,它只是在内存中的 the_strange_sum 对象中记录一个整数。看来我们理解的词不一样。
          • 同意。在您包含 . 之前,我无法理解您的观点
          【解决方案10】:

          LineSentence 模块呢,它会忽略这些行:

          基础:对象

          简单格式:一句话=一行;已经预处理的单词和 用空格分隔。

          source 可以是字符串或文件对象。将文件剪辑到 第一条限制线(如果限制为无,则不剪裁,默认)。

          from gensim.models.word2vec import LineSentence
          text = LineSentence('text.txt')
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2010-12-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-10-18
            • 1970-01-01
            相关资源
            最近更新 更多