跳过标题行的更多pythonic方式

时间:2009-11-13 17:12:25

标签: python

是否有更短(或许更pythonic)的方式打开文本文件并读取以注释字符开头的行?

换句话说,这是一种更简洁的方式

fin = open("data.txt")
line = fin.readline()
while line.startswith("#"):
    line = fin.readline()

9 个答案:

答案 0 :(得分:16)

在我学习Python的这个阶段,我发现这个最Pythonic:

def iscomment(s):
   return s.startswith('#')

from itertools import dropwhile
with open(filename, 'r') as f:
    for line in dropwhile(iscomment, f):
       # do something with line

跳过以#开头的文件顶部的所有行。要跳过以#开头的所有行:

from itertools import ifilterfalse
with open(filename, 'r') as f:
    for line in ifilterfalse(iscomment, f):
       # do something with line

几乎所有关于我的可读性;功能上几乎没有区别:

for line in ifilterfalse(iscomment, f))

for line in (x for x in f if not x.startswith('#'))

将测试分解为自己的功能使代码的意图更加清晰;这也意味着如果你对评论的定义发生了变化,你就有一个地方可以改变它。

答案 1 :(得分:14)

for line in open('data.txt'):
    if line.startswith('#'):
        continue
    # work with line

当然,如果您的注释行仅位于文件的开头,则可以使用一些优化。

答案 2 :(得分:10)

from itertools import dropwhile
for line in dropwhile(lambda line: line.startswith('#'), file('data.txt')):
    pass

答案 3 :(得分:6)

如果您要过滤掉所有注释行(不仅仅是文件开头的注释行):

for line in file("data.txt"):
  if not line.startswith("#"):
    # process line

如果您只想在开始时跳过这些内容,请使用itertools.dropwhile

查看ephemient的回答

答案 4 :(得分:5)

您可以使用生成器功能

def readlines(filename):
    fin = open(filename)
    for line in fin:
        if not line.startswith("#"):
            yield line

并像

一样使用它
for line in readlines("data.txt"):
    # do things
    pass

根据文件的确切位置,您可能还需要在strip()检查之前startswith()行。我曾经不得不在编写脚本之后几个月调试脚本,因为有人在'#'之前放了几个空格字符

答案 5 :(得分:5)

作为一个实际问题,如果我知道我正在处理合理大小的文本文件(任何能够很好地适应内存的东西)那么我会遇到类似的问题:

f = open("data.txt")
lines = [ x for x in f.readlines() if x[0] != "#" ]

...在整个文件中进行snarf并过滤掉以octothorpe开头的所有行。

正如其他人所指出的那样,人们可能希望忽略在octothorpe之前出现的前导空格,如下所示:

lines = [ x for x in f.readlines() if not x.lstrip().startswith("#") ]

我喜欢这个简洁。

这假设我们要删除所有注释行。

我们还可以使用以下方式“删除”每个字符的末尾(几乎总是换行符):

lines = [ x[:-1] for x in ... ]

...假设我们并不担心文件最后一行丢失最终换行的臭名昭着的问题。 (.readlines()或类似文件类对象方法中的一行可能不会以换行符结尾的唯一时间是EOF)。

在最新版本的Python中,可以使用条件表达式“删除”(仅仅是换行符)行的末尾:

lines = [ x[:-1] if x[-1]=='\n' else x for x in ... ]

...这就像我为清晰易读而列出的列表理解一样复杂。

如果我们担心文件过大(或内存限制不足)可能会影响我们的性能或稳定性,而且我们正在使用最近足以支持生成器表达式的Python版本(这是最新增加的语言,而不是我在这里使用的列表理解),然后我们可以使用:

for line in (x[:-1] if x[-1]=='\n' else x for x in
  f.readlines() if x.lstrip().startswith('#')):

    # do stuff with each line

...是在我们希望其他人在代码签入后一年内在一行中进行解析的极限。

如果意图只是跳过“标题”行,那么我认为最好的方法是:

f = open('data.txt')
for line in f:
    if line.lstrip().startswith('#'):
        continue

......并完成它。

答案 6 :(得分:4)

你可以制作一个循环文件​​的生成器,跳过这些行:

fin = open("data.txt")
fileiter = (l for l in fin if not l.startswith('#'))

for line in fileiter:
   ...

答案 7 :(得分:2)

您可以执行类似

的操作
def drop(n, seq):
    for i, x in enumerate(seq):
        if i >= n:
            yield x

然后说

for line in drop(1, file(filename)):
    # whatever

答案 8 :(得分:2)

我喜欢@ iWerner的发电机功能理念。对他的代码做了一个小改动,它就是问题所要求的。

def readlines(filename):
    f = open(filename)
    # discard first lines that start with '#'
    for line in f:
        if not line.lstrip().startswith("#"):
            break
    yield line

    for line in f:
        yield line

并像

一样使用它
for line in readlines("data.txt"):
    # do things
    pass

但这是一种不同的方法。这几乎非常简单。我们的想法是打开文件,获取一个文件对象,我们可以将它用作迭代器。然后我们从迭代器中拉出我们不想要的行,然后返回迭代器。如果我们总是知道要跳过多少行,这将是理想的。这里的问题是我们不知道需要跳过多少行;我们只需要划线并看看它们。一旦我们拉了它,就没有办法把线放回到迭代器中。

所以:打开迭代器,拉线和计算有多少具有前导'#'字符;然后使用.seek()方法回滚文件,再次提取正确的数字,然后返回迭代器。

我喜欢这样的一件事:你用它的所有方法得到实际的文件对象;您可以使用此代替open(),它可以在所有情况下使用。我将该函数重命名为open_my_text()以反映这一点。

def open_my_text(filename):
    f = open(filename, "rt")
    # count number of lines that start with '#'
    count = 0
    for line in f:
        if not line.lstrip().startswith("#"):
            break
        count += 1

    # rewind file, and discard lines counted above
    f.seek(0)
    for _ in range(count):
        f.readline()

    # return file object with comment lines pre-skipped
    return f

而不是f.readline()我可以使用f.next()(对于Python 2.x)或next(f)(对于Python 3.x)但是我想写它以便它可以移植到任何Python。

编辑:好的,我知道没有人关心,我没有得到任何赞成,但我最后一次重写了我的答案,使其更优雅。

您不能将一行返回到迭代器中。但是,你可以打开一个文件两次,并得到两个迭代器;鉴于文件缓存的工作方式,第二个迭代器几乎是免费的。如果我们想象一个顶部带有兆字节“#”行的文件,这个版本将大大优于调用f.seek(0)的先前版本。

def open_my_text(filename):
    # open the same file twice to get two file objects
    # (We are opening the file read-only so this is safe.)
    ftemp = open(filename, "rt")
    f = open(filename, "rt")

    # use ftemp to look at lines, then discard from f
    for line in ftemp:
        if not line.lstrip().startswith("#"):
            break
        f.readline()

    # return file object with comment lines pre-skipped
    return f

这个版本比以前的版本要好得多,它仍然会返回一个包含所有方法的完整文件对象。