49

在 Python 中,我刚刚从文本文件中读取了一行,我想知道如何编写代码以忽略行首带有哈希 # 的注释。

我认为应该是这样的:

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

但我是 Python 新手,我不知道语法

4

10 回答 10

66

你可以使用startswith()

例如

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()
于 2009-11-10T07:38:47.150 回答
46

#我建议您在看到字符时不要忽略整行;只需忽略该行的其余部分。您可以使用名为的字符串方法函数轻松地做到这一点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 个子字符串(通过用 索引[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

我真的不想在一个标记为“初学者”的问题中弄得这么复杂,但是这个状态机相当简单,我希望它会很有趣。

于 2009-11-10T08:13:19.953 回答
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 
于 2014-11-27T21:13:28.513 回答
8

这是可能的最短形式:

for line in open(filename):
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE

startswith()如果您调用它的字符串以您传入的字符串开头,则字符串上的方法返回 True。

虽然这在某些情况下(如 shell 脚本)是可以的,但它有两个问题。首先,它没有指定如何打开文件。打开文件的默认模式是'r',这意味着“以二进制模式读取文件”。由于您需要一个文本文件,因此最好使用'rt'. 尽管这种区别在类 UNIX 操作系统上无关紧要,但在 Windows(以及 OS X 之前的 Mac)上很重要。

第二个问题是打开文件句柄。该open()函数返回一个文件对象,并且在完成文件后关闭文件被认为是一种好习惯。为此,请调用close()对象上的方法。现在,Python 最终可能会为你做这件事在 Python 中,对象是引用计数的,当对象的引用计数变为零时,它会被释放,并且在对象被释放后的某个时刻,Python 将调用其析构函数(一种称为 的特殊方法__del__)。请注意,我可能说过: Python 有一个坏习惯,即在程序结束前不久对引用计数降至零的对象不调用析构函数。估计是赶时间!

对于像 shell 脚本这样的短期程序,尤其是文件对象,这无关紧要。当程序完成时,您的操作系统将自动清理所有打开的文件句柄。但是,如果您打开文件,读取内容,然后开始长时间计算而没有先显式关闭文件句柄,Python 可能会在计算期间使文件句柄保持打开状态。这是不好的做法。

此版本适用于任何 2.x 版本的 Python,并修复了我上面讨论的两个问题:

f = open(file, 'rt')
for line in f:
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE
f.close()

这是旧版本 Python 的最佳通用形式。

正如 steveha 所建议的,使用“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 中,字符串是不可变的,所以这不会改变line. 该lstrip()方法返回删除了所有前导空格的字符串的副本。

于 2009-11-11T14:40:08.473 回答
5

我最近发现生成器函数在这方面做得很好。我使用过类似的功能来跳过注释行、空白行等。

我将我的功能定义为

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

这可以在我的所有代码中重用,我可以添加任何额外的处理/日志记录/等。我需要的。

于 2012-06-20T22:36:14.113 回答
5

我知道这是一个旧线程,但这是一个生成器函数,用于我自己的目的。无论注释出现在行中的什么位置,它都会去除注释,并去除前导/尾随空格和空白行。以下源文本:

# 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 uncommented non-blank line from ``item`` with
            comments and leading/trailing whitespace stripped.
    
    """
    
    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!""".split('\n')

    
    hosts = strip_comments(HOSTS)
    print('\n'.join(h for h in hosts))

正常的用例是从文件(即主机文件,如我上面的示例中)中删除注释。如果是这种情况,那么上述代码的尾部将被修改为:

if __name__ == '__main__':
    with open('aa.txt', 'r') as f:
        hosts = strip_comments(f)

        for host in hosts:
            print('\'%s\'' % host)
于 2015-03-09T20:01:45.190 回答
3

过滤表达式的更紧凑版本也可以如下所示:

for line in (l for l in open(filename) if not l.startswith('#')):
    # do something with line

(l for ... )被称为“生成器表达式”,它在这里充当包装迭代器,在迭代文件时将过滤掉文件中所有不需要的行。不要将它与方括号中的相同内容混淆,[l for ... ]这是一种“列表理解”,它将首先将文件中的所有行读入内存,然后才会开始对其进行迭代。

有时您可能希望它更少单行且更具可读性:

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

所有过滤器将在一次迭代中即时执行。

于 2009-11-10T14:08:59.243 回答
2

使用正则表达式re.compile("^(?:\s+)*#|(?:\s+)")跳过新行和注释。

于 2017-03-10T20:34:09.440 回答
1

我倾向于使用

for line  in lines:
    if '#' not in line:
        #do something

这将忽略整行,尽管包含 rpartition 的答案有我的支持,因为它可以包含 # 之前的任何信息

于 2009-11-17T00:27:07.580 回答
1

删除适用于内联和在线的评论是一件好事

def clear_coments(f):
    new_text = ''
    for line in f.readlines():
        if "#" in line: line = line.split("#")[0]

        new_text += line

    return new_text
于 2020-12-21T09:54:00.827 回答