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

Joh*_*n 41 python string file-io comments skip

在Python中,我刚刚读了一个文本文件的行,我想知道如何编写代码来忽略带有#行开头的#的注释.

我认为它应该是这样的:

for 
   if line !contain #
      then ...process line
   else end for loop 
Run Code Online (Sandbox Code Playgroud)

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

gho*_*g74 55

你可以使用startswith()

例如

for line in open("file"):
    li=line.strip()
    if not li.startswith("#"):
        print line.rstrip()
Run Code Online (Sandbox Code Playgroud)

  • 你的代码有`for line in open("file"):`它留下一个打开的文件句柄.你应该保持`open("file")`的返回值,并在你完成后显式地调用`close()`,或者使用`with`语句(参见http://docs.python.org/)库/ stdtypes.html#file.close). (11认同)
  • 这并没有真正留下一个打开的文件句柄,至少在CPython中.当对文件对象的最后一个引用消失时,文件对象将被垃圾收集,此时文件将被关闭.Jython(在Java VM上运行)可能会有所不同.如果你使用的是带有`with`语句的现代Python,那么将`with open("filename")用作f:`然后用`f`(或任何其他语言)引用文件对象被认为是非常好的形式.您可能选择的变量名称).`with`将确保文件关闭,无论如何,即使面对异常. (6认同)
  • 不,不应该.当EOF时,for循环将隐式调用StopIteration. (5认同)
  • ...忽略前导空格:`if not line.strip().startswith("#")` (4认同)

ste*_*eha 43

我建议你在看到一个#角色时不要忽略整行; 只是忽略其余部分.您可以使用名为的字符串方法函数轻松完成此操作partition:

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

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 ...
Run Code Online (Sandbox Code Playgroud)

这会将字符串拆分为"#"字符,然后在拆分之前保留所有内容.该1参数使得.split()方法在一次拆分后停止; 因为我们只是抓住第0个子字符串(通过索引[0]),你可以在没有1参数的情况下获得相同的答案,但这可能会更快一些.(感谢来自@gnr的评论,从我的原始代码中简化.我的原始代码因为没有充分理由而变得更加混乱;谢谢,@ ngr.)

您也可以编写自己的版本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:])
Run Code Online (Sandbox Code Playgroud)

@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
Run Code Online (Sandbox Code Playgroud)

我真的不想在一个标记为"初学者"的问题中把这个复杂化,但这个状态机相当简单,我希望它会很有趣.

  • 是的,但是如果你想要正确的话,你可能还需要关注引用的#. (2认同)

tra*_*avc 8

我很晚才到,但是处理shell样式(或python样式)#注释的问题是非常常见的.

我几乎每次阅读文本文件时都会使用一些代码.
问题是它没有正确处理引用或转义的注释.但它适用于简单的情况并且很容易.

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

更强大的解决方案是使用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
Run Code Online (Sandbox Code Playgroud)

这种shlex方法不仅可以正确处理引号和转义,还增加了很多很酷的功能(比如能够让文件根据需要提供其他文件).我还没有测试它在大文件上的速度,但它足够小的东西.

当你将每个输入行分成字段(在空格上)时,常见的情况甚至更简单:

import shlex
for line in instream:
    fields = shlex.split(line, comments=True)
    if not fields:
        continue
    # process list of fields 
Run Code Online (Sandbox Code Playgroud)


Lar*_*ngs 6

这是最短的形式:

for line in open(filename):
  if line.startswith('#'):
    continue
  # PROCESS LINE HERE
Run Code Online (Sandbox Code Playgroud)

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

虽然在shell脚本等某些情况下这是可以的,但它有两个问题.首先,它没有指定如何打开文件.打开文件的默认模式是'r'"以二进制模式读取文件".由于您期望一个文本文件,最好用它打开它'rt'.虽然这种区别与类UNIX操作系统无关,但它在Windows(以及OS-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()
Run Code Online (Sandbox Code Playgroud)

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

正如steveha所建议的,使用"with"语句现在被认为是最佳实践.如果你使用2.6或更高版本,你应该这样写:

with open(filename, 'rt') as f:
  for line in f:
    if line.startswith('#'):
      continue
    # PROCESS LINE HERE
Run Code Online (Sandbox Code Playgroud)

"with"语句将为您清理文件句柄.

在你的问题中,你说"以#开头的行",这就是我在这里向你展示的内容.如果您想筛选出这样开始的行可选空白,并以"#",你应该寻找"#"之前剥去空白.在这种情况下,你应该改变这个:

    if line.startswith('#'):
Run Code Online (Sandbox Code Playgroud)

对此:

    if line.lstrip().startswith('#'):
Run Code Online (Sandbox Code Playgroud)

在Python中,字符串是不可变的,因此这不会改变它的值line.该lstrip()方法返回字符串的副本,并删除其所有前导空格.


Tim*_*omb 5

我最近发现生成器功能可以很好地完成这项工作.我使用类似的功能来跳过注释行,空行等.

我将我的功能定义为

def skip_comments(file):
    for line in file:
        if not line.strip().startswith('#'):
            yield line
Run Code Online (Sandbox Code Playgroud)

这样,我就可以做到

f = open('testfile')
for line in skip_comments(f):
    print line
Run Code Online (Sandbox Code Playgroud)

这可以在我的所有代码中重用,我可以添加任何其他处理/日志/等.我需要的.


Dou*_* R. 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!
  
Run Code Online (Sandbox Code Playgroud)

将产生:

host02
host03
host04
Run Code Online (Sandbox Code Playgroud)

这是记录的代码,其中包括演示:

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))
Run Code Online (Sandbox Code Playgroud)

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

# 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!
  
Run Code Online (Sandbox Code Playgroud)