Python:如何在文件中读取时忽略#注释行
在Python中,我刚刚读了一个文本文件的行,我想知道如何编码,以忽略在行开头的散列#注释。
我认为这应该是这样的:
for if line !contain # then ...process line else end for loop
但我是Python的新手,我不知道这个语法
你可以使用startswith()
例如
for line in open("file"): li=line.strip() if not li.startswith("#"): print line.rstrip()
我build议你在看到一个#
字符时不要忽略整个行; 只是忽略其余的行。 你可以用一个称为partition
的string方法函数轻松地做到这一点
with open("filename") as f: for line in f: line = line.partition('#')[0] line = line.rstrip() # ... do something with line ...
partition
返回一个元组:分区string之前的所有内容,分区string以及分区string之后的所有内容。 因此,通过索引[0]
我们只取出分区string之前的部分。
编辑:如果您正在使用没有partition()
的Python版本,这里是您可以使用的代码:
with open("filename") as f: for line in f: line = line.split('#', 1)[0] line = line.rstrip() # ... do something with line ...
这将string分割成“#”字符,然后在分割之前保留所有内容。 1
参数使.split()
方法在一次分割后停止; 因为我们只是抓住第0个子string(通过索引[0]
),你会得到同样的答案没有1
参数,但这可能会更快一点。 (从我的原始代码简化感谢来自@gnr的评论。我原来的代码是没有理由的messier;谢谢,@gnr。)
你也可以写你自己的partition()
版本partition()
。 这里有一个叫做part()
:
def part(s, s_part): i0 = s.find(s_part) i1 = i0 + len(s_part) return (s[:i0], s[i0:i1], s[i1:])
@dalle指出'#'可以出现在一个string中。 要正确处理这个案子并不是那么容易,所以我只是不理它,但我应该说点什么。
如果你的input文件对引用的string有足够简单的规则,这并不难。 如果您接受任何合法的Python引用string,将会很困难,因为有单引号,双引号,多行引号和反斜杠转义行尾,三引号string(使用单引号或双引号),以及甚至原始的string 正确处理所有这些将是一个复杂的状态机的唯一可能的方法。
但是,如果我们仅限于一个简单的引用string,我们可以用一个简单的状态机来处理它。 我们甚至可以在string中加一个反斜杠的双引号。
c_backslash = '\\' c_dquote = '"' c_comment = '#' def chop_comment(line): # a little state machine with two state varaibles: in_quote = False # whether we are in a quoted string right now backslash_escape = False # true if we just saw a backslash for i, ch in enumerate(line): if not in_quote and ch == c_comment: # not in a quote, saw a '#', it's a comment. Chop it and return! return line[:i] elif backslash_escape: # we must have just seen a backslash; reset that flag and continue backslash_escape = False elif in_quote and ch == c_backslash: # we are in a quote and we see a backslash; escape next char backslash_escape = True elif ch == c_dquote: in_quote = not in_quote return line
我真的不想在标签为“新手”的问题中弄得这么复杂,但是这个状态机是相当简单的,我希望它会很有趣。
这是最短的forms:
for line in open(filename): if line.startswith('#'): continue # PROCESS LINE HERE
一个string上的startswith()
方法返回True,如果你调用它的string以你传入的string开始。
虽然在某些情况下(比如shell脚本)这是可以的,但它有两个问题。 首先,它没有指定如何打开文件。 打开文件的默认模式是'r'
,意思是“以二进制模式读取文件”。 既然你期待一个文本文件,最好用'rt'
打开它。 虽然这种区别在类UNIX操作系统上是无关紧要的,但在Windows(以及在Mac OS X之前)是很重要的。
第二个问题是打开的文件句柄。 open()
函数返回一个文件对象,当你完成它时closures文件被认为是一个很好的习惯。 为此,请在对象上调用close()
方法。 现在,Python最终可能会为你做这个, 在Python中,对象是引用计数的,当一个对象的引用计数变为零时,它将被释放,并且在释放一个对象之后的某一时刻,Python会调用它的析构函数(一个叫做__del__
的特殊方法)。 请注意,我可能说: Python有一个坏习惯,实际上不是在程序完成之前,对其引用计数下降到零的对象实际调用析构函数。 我猜这是匆忙!
对于像shell脚本这样的短命程序,特别是对于文件对象来说,这并不重要。 程序结束后,您的操作系统将自动清除所有打开的文件句柄。 但是,如果您打开文件,读取内容,然后开始一个长计算,而不首先明确closures文件句柄,则Python可能会在计算过程中使文件句柄保持打开状态。 这是不好的做法。
这个版本可以在Python的任何2.x版本中使用,并修复了上面讨论的两个问题:
f = open(file, 'rt') for line in f: if line.startswith('#'): continue # PROCESS LINE HERE f.close()
这是老版本Python的最佳通用forms。
正如steveha所build议的那样,使用“with”语句现在被认为是最佳实践。 如果你使用2.6或以上版本,你应该这样写:
with open(filename, 'rt') as f: for line in f: if line.startswith('#'): continue # PROCESS LINE HERE
“with”语句将为您清理文件句柄。
在你的问题中,你说“以#开始的行”,所以这就是我在这里给你看的。 如果要过滤掉以可选空格开始并以'#'开头的行, 则在查找“#”之前,应该先去掉空格。 在这种情况下,你应该改变这个:
if line.startswith('#'):
对此:
if line.lstrip().startswith('#'):
在Python中,string是不可变的,所以这不会改变行的值。 lstrip()
方法返回一个string的副本,并删除所有前导空白。
我最近发现一个生成器函数在这方面做得很好。 我使用了类似的function来跳过注释行,空白行等
我把我的function定义为
def skip_comments(file): for line in file: if not line.strip().startswith('#'): yield line
那样,我可以做
f = open('testfile') for line in skip_comments(f): print line
这是可重用的所有我的代码,我可以添加任何额外的处理/日志/等。 我需要的。
我来晚了,但处理shell风格(或python风格) #
评论的问题是一个非常普遍的问题。
几乎每次我读一个文本文件时,我一直在使用一些代码。
问题是它不能正确处理引用或转义的注释 。 但它适用于简单的情况,很容易。
for line in whatever: line = line.split('#',1)[0].strip() if not line: continue # process line
更强大的解决scheme是使用shlex :
import shlex for line in instream: lex = shlex.shlex(line) lex.whitespace = '' # if you want to strip newlines, use '\n' line = ''.join(list(lex)) if not line: continue # process decommented line
这种shlex方法不仅可以正确处理引号和转义,还可以添加很多很酷的function(比如,如果需要,可以使文件生成其他文件)。 我还没有testing它在大文件上的速度,但它足够小的东西。
当你也将每个input行分割成字段(空格)的情况更为简单:
import shlex for line in instream: fields = shlex.split(line, comments=True) if not fields: continue # process list of fields
过滤expression式的更紧凑版本也可以像这样:
for line in (l for l in open(filename) if not l.startswith('#')): # do something with line
(l for ... )
被称为“生成器expression式”(generator expression),它在此处作为一个包装迭代器,在遍历文件时将从文件中滤除所有不需要的行。 不要把它和方括号中的同样的东西混淆[l for ... ]
,这是一个“列表理解”,它将首先从文件中读取所有的行到内存中,然后才开始迭代它。
有时你可能希望减less一行和更多的可读性:
lines = open(filename) lines = (l for l in lines if ... ) # more filters and mappings you might want for line in lines: # do something with line
所有的filter将在一次迭代中被dynamic执行。
我知道这是一个旧的线程,但这是一个生成器函数,我用于我自己的目的。 无论它们出现在行中的哪个位置,它都会删除注释,以及删除前导/尾随空白和空白行。 以下源文本:
# Comment line 1 # Comment line 2 # host01 # This host commented out. host02 # This host not commented out. host03 host04 # Oops! Included leading whitespace in error!
会产生:
host02 host03 host04
这里是文档化的代码,其中包括一个演示:
def strip_comments(item, *, token='#'): """Generator. Strips comments and whitespace from input lines. This generator strips comments, leading/trailing whitespace, and blank lines from its input. Arguments: item (obj): Object to strip comments from. token (str, optional): Comment delimiter. Defaults to ``#``. Yields: str: Next non-blank line from ``item`` with comments and leading/trailing whitespace removed. """ for line in item: s = line.split(token, 1)[0].strip() if s != '': yield s if __name__ == '__main__': HOSTS = ['# Comment line 1', '# Comment line 2', '', '# host01 # This host commented out.', 'host02 # This host not commented out.', 'host03', ' host04 # Oops! Included leading whitespace in error!',] hosts = strip_comments(HOSTS) for host in hosts: print('\'%s\'' % host)
正常的使用情况是从文件中去除注释(例如,上面的示例中的主机文件)。 如果是这种情况,则上述代码的尾部将被修改为:
if __name__ == '__main__': with open('hosts.txt', 'r') as f: hosts = strip_comments(f) for host in hosts: print('\'%s\'' % host)
使用正则expression式re.compile("^(?:\s+)*#|(?:\s+)")
来跳过新的行和注释。
我倾向于使用
for line in lines: if '#' not in line: #do something
这将忽略整个行,虽然包括分裂的答案有我的赞成,因为它可以包括来自#