有没有更简短(也许更符合Python风格)的方法打开文本文件并读取不是以注释字符开头的行?
换句话说,有没有更简洁的方法实现这个功能:
fin = open("data.txt")
line = fin.readline()
while line.startswith("#"):
line = fin.readline()
有没有更简短(也许更符合Python风格)的方法打开文本文件并读取不是以注释字符开头的行?
换句话说,有没有更简洁的方法实现这个功能:
fin = open("data.txt")
line = fin.readline()
while line.startswith("#"):
line = fin.readline()
在我学习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('#'))
将测试分解成自己的函数可以使代码的意图更清晰;这也意味着,如果您对评论的定义发生变化,您只需更改一个位置。
while
应该改成 with
,对吗? - Autoplecticfilterfalse
而不是ifilterfalse
。 - Jadimfor line in open('data.txt'):
if line.startswith('#'):
continue
# work with line
当然,如果你的注释行只在文件开头,那么你可以使用一些优化。
from itertools import dropwhile
for line in dropwhile(lambda line: line.startswith('#'), file('data.txt')):
pass
for line in file("data.txt"):
if not line.startswith("#"):
# process line
如果你只想跳过开头的那些元素,请参考ephemient使用itertools.dropwhile
的答案。
您可以使用生成器函数
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
根据文件来源的不同,您可能还需要在使用startswith()
检查之前对行进行strip()
操作。我曾经不得不在几个月后调试这样的脚本,因为有人在'#'前放了几个空格字符。
#
开头的行,而不仅仅是文件开头(“head”)的那些——OP对所需行为并不完全清楚。 - ephemientfor line in (line for line in open('data.txt') if not line.startswith('#')):
- ephemientf = open("data.txt")
lines = [ x for x in f.readlines() if x[0] != "#" ]
...读取整个文件并过滤掉以井号开头的所有行。
正如其他人指出的那样,我们可能希望忽略在井号之前出现的前导空格,就像这样:
lines = [ x for x in f.readlines() if not x.lstrip().startswith("#") ]
我喜欢这个的简洁性。
这假设我们想要剥离所有注释行。
我们也可以使用以下方法“截断”每个字符串结尾的最后几个字符(几乎总是换行符):
lines = [ x[:-1] for x in ... ]
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
... 干完就完了。
fin = open("data.txt")
fileiter = (l for l in fin if not l.startswith('#'))
for line in fileiter:
...
def drop(n, seq):
for i, x in enumerate(seq):
if i >= n:
yield x
然后说
for line in drop(1, file(filename)):
# whatever
我喜欢@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.next()
(对于Python 2.x)或next(f)
(对于Python 3.x),而不是f.readline()
,但我想编写一个可移植到任何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
这个版本比之前的版本好得多,它仍然返回一个完整的文件对象,包括所有的方法。
f.tell()
来保存文件中实际的位置,而不是使用计数器?将 count = 0
替换为 loc = 0
,将 count += 1
替换为 loc = f.tell()
,将 f.seek(0)
替换为 f.seek(loc)
,并完全删除 for _ in range(count)
循环。 - PaulMcG.tell()
方法与迭代器不匹配;我的短测试文件完全被吞掉了,每次调用.tell()
都返回文件末尾。如果.tell()
能够跟踪迭代器,我绝对会按照你的方式去做;这样更加简洁。我的代码比较混乱,但它的优点是实际上可以工作... :-) - steveha