Python:如何在读取文件时忽略#注释行

49 投票
10 回答
121109 浏览
提问于 2025-04-15 15:50

在Python中,我刚刚从一个文本文件中读取了一行内容,我想知道怎么写代码来忽略以#号开头的注释行。

我觉得应该像这样:

for 
   if line !contain #
      then ...process line
   else end for loop 

但是我对Python还很陌生,不太懂语法。

10 个回答

9

我来得有点晚,但处理 shell 风格(或 Python 风格)的 # 注释的问题是一个非常常见的情况。

我几乎每次读取文本文件时都会用到一些代码。
问题是它不能正确处理带引号或转义的注释。不过对于简单的情况,它还是能用的,而且很简单。

for line in whatever:
    line = line.split('#',1)[0].strip()
    if not line:
        continue
    # process line

一个更可靠的解决方案是使用 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 方法不仅能正确处理引号和转义字符,还增加了很多很酷的功能(比如如果你想的话,可以让文件引用其他文件)。我还没有在大文件上测试它的速度,但在小文件上它的速度还不错。

当你还需要把每一行输入按空格分割成字段时,情况会更简单:

import shlex
for line in instream:
    fields = shlex.split(line, comments=True)
    if not fields:
        continue
    # process list of fields 
48

我建议你看到 # 字符时,不要忽略整行,只需忽略这一行的其余部分。你可以使用一个叫做 partition 的字符串方法来轻松做到这一点:

with open("filename") as f:
    for line in f:
        line = line.partition('#')[0]
        line = line.rstrip()
        # ... do something with line ...

partition 会返回一个元组:在分隔字符串之前的所有内容、分隔字符串本身,以及在分隔字符串之后的所有内容。所以,通过使用 [0] 来索引,我们可以只获取分隔字符串之前的部分。

编辑: 如果你使用的 Python 版本没有 partition() 方法,这里有一段代码可以使用:

with open("filename") as f:
    for line in f:
        line = line.split('#', 1)[0]
        line = line.rstrip()
        # ... do something with line ...

这段代码会在 # 字符上分割字符串,然后保留分割前的所有内容。1 这个参数让 .split() 方法在分割一次后停止;因为我们只是获取第一个子字符串(通过 [0] 索引),即使没有 1 参数也能得到相同的结果,不过这样可能会稍微快一点。(感谢 @gnr 的评论,我简化了我原来的代码,之前的代码有点乱,没必要;谢谢你,@gnr。)

你也可以自己写一个 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 提到过 # 可能会出现在字符串内部。处理这种情况并不简单,所以我选择忽略它,但我应该提一下。

如果你的输入文件对带引号的字符串有简单的规则,这并不难。如果你接受任何合法的 Python 带引号字符串,那就会很麻烦,因为有单引号、双引号、多行引号(用反斜杠转义换行符)、三重引号(可以是单引号或双引号),甚至还有原始字符串!正确处理这些情况的唯一方法就是使用复杂的状态机。

但如果我们限制在简单的带引号字符串,我们可以用简单的状态机来处理。我们甚至可以允许在字符串内部使用反斜杠转义的双引号。

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

我其实不想在一个标记为“初学者”的问题中搞得这么复杂,但这个状态机相对简单,我希望它会引起你的兴趣。

68

你可以使用 startswith() 这个方法。

比如:

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()

撰写回答