【问题标题】:Python: Read text file into dict and ignore commentsPython:将文本文件读入dict并忽略注释
【发布时间】:2016-05-05 18:24:09
【问题描述】:

我正在尝试将以下文本文件放入字典中,但我希望忽略以“#”开头的任何部分或空行。

我的文本文件如下所示:

# This is my header info followed by an empty line

Apples          1                # I want to ignore this comment
Oranges         3                # I want to ignore this comment

#~*~*~*~*~*~*~*Another comment~*~*~*~*~*~*~*~*~*~*

Bananas         5                # I want to ignore this comment too!

我想要的输出是:

myVariables = {'Apples': 1, 'Oranges': 3, 'Bananas': 5}

我的 Python 代码如下:

filename = "myFile.txt"
myVariables = {}

with open(filename) as f:
    for line in f:
        if line.startswith('#') or not line:
            next(f)

        key, val = line.split()
        myVariables[key] = val
        print "key: " + str(key) + " and value: " + str(val)

我得到的错误:

Traceback (most recent call last):
  File "C:/Python27/test_1.py", line 11, in <module>
    key, val = line.split()
ValueError: need more than 1 value to unpack

我理解错误,但我不明白代码有什么问题。

提前谢谢你!

【问题讨论】:

  • 您可以控制文本文件格式吗?如果是这样,也许考虑使用 PyYAML?
  • 遗憾的是没有。文本格式是我被指示使用的格式。
  • 你检查过答案吗?已经给出了一些好的方法。浏览它们并询问您是否需要更多信息。
  • @Pouria Hadjibagheri:是的,绝对好的方法,谢谢!我们说话的时候我正在经历它们:)
  • 我找到了答案。感谢大家非常快速和有用的回复/见解!

标签: python dictionary text comments


【解决方案1】:

鉴于您的文字:

text = """
# This is my header info followed by an empty line

Apples          1                # I want to ignore this comment
Oranges         3                # I want to ignore this comment

#~*~*~*~*~*~*~*Another comment~*~*~*~*~*~*~*~*~*~*

Bananas         5                # I want to ignore this comment too!
"""

我们可以通过两种方式做到这一点。使用regex,或使用 Python 生成器。我会选择后者(如下所述),因为 regex 在这种情况下并不是特别快(呃)。

打开文件:

with open('file_name.xyz', 'r') as file: 
    # everything else below. Just substitute `for line in lines` with 
    # `for line in file.readline()`

现在要创建一个类似的,我们拆分行,并创建一个列表:

lines = text.split('\n')  # as if read from a file using `open`. 

以下是我们如何在几行代码中完成您想要的所有操作:

# Discard all comments and empty values.
comment_less = filter(None, (line.split('#')[0].strip() for line in lines))

# Separate items and totals. 
separated = {item.split()[0]: int(item.split()[1]) for item in comment_less}

让我们测试一下:

>>> print(separated)
{'Apples': 1, 'Oranges': 3, 'Bananas': 5}

希望这会有所帮助。

【讨论】:

    【解决方案2】:

    这并不能完全重现您的错误,但您的代码存在问题:

    >>> x = "Apples\t1\t# This is a comment"
    >>> x.split()
    ['Apples', '1', '#', 'This', 'is', 'a', 'comment']
    >>> key, val = x.split()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    ValueError: too many values to unpack
    

    改为尝试:

    key = line.split()[0]
    val = line.split()[1]
    

    编辑:我认为您的“需要超过 1 个值才能解压”来自空白行。另外,我不熟悉像这样使用next()。我想我会这样做:

    if line.startswith('#') or line == "\n":
        pass
    else:
        key = line.split()[0]
        val = line.split()[1]
    

    【讨论】:

    • 我只是在使用 next() ,因为有人在另一篇文章中建议了它。我会尝试使用 pass 代替。谢谢!
    【解决方案3】:

    要剥离 cmets,您可以使用 str.partition(),无论该行中是否存在注释符号:

    for line in file:
        line, _, comment = line.partition('#')
        if line.strip(): # non-blank line
            key, value = line.split()
    

    line.split() 也可能在这段代码中引发异常——如果有一个非空行不包含两个空格分隔的单词,就会发生这种情况——这取决于应用程序在这种情况下你想要做什么(忽略例如行、打印警告等)。

    【讨论】:

    • 不错的方法。对于这个特殊问题,我不会想到 str.partition()
    【解决方案4】:

    您需要忽略空行和以# 开头的行,在# 上拆分或使用 rfind 如下分割字符串后分割剩余的行,空行将有一个新行,所以你需要and line.strip() 来检查一个,你不能只在空格上拆分并解压缩,因为拆分后你有两个以上的元素,包括评论中的内容:

    with open("in.txt") as f:
        d = dict(line[:line.rfind("#")].split() for line in f
                  if not line.startswith("#") and line.strip())
        print(d)
    

    输出:

    {'Apples': '1', 'Oranges': '3', 'Bananas': '5'}
    

    另一种选择是拆分两次并切片:

    with open("in.txt") as f:
        d = dict(line.split(None,2)[:2] for line in f
                  if not line.startswith("#") and line.strip())
        print(d)
    

    或者使用显式循环拆分两次并解包:

    with open("in.txt") as f:
        d = {}
        for line in f:
            if not line.startswith("#") and line.strip():
                k, v, _ = line.split(None, 2)
                d[k] = v
    

    您也可以使用 itertools.groupby 对您想要的行进行分组。

    from itertools import groupby
    with open("in.txt") as f:
        grouped = groupby(f, lambda x: not x.startswith("#") and x.strip())
        d = dict(next(v).split(None, 2)[:2] for k, v in grouped if k)
        print(d)
    

    要处理单引号中有多个单词的情况,我们可以使用 shlex 进行拆分:

    import shlex
    with open("in.txt") as f:
        d = {}
        for line in f:
            if not line.startswith("#") and line.strip():
                data = shlex.split(line)
                d[data[0]] = data[1]
    
    print(d)
    

    因此将 Banana 行更改为:

     Bananas          'north-side disabled'                # I want to ignore this comment too!
    

    我们得到:

    {'Apples': '1', 'Oranges': '3', 'Bananas': 'north-side disabled'}
    

    同样适用于切片:

    with open("in.txt") as f:
        d = dict(shlex.split(line)[:2] for line in f
                  if not line.startswith("#") and line.strip())
        print(d)
    

    【讨论】:

    • 嗨帕德莱克!您的“拆分两次并切片”方法对我来说效果最好。我忘了提到一些第二列值有时在字符串周围有单引号,例如:'north-side disabled'。
    • @SilverEyes9,shlex 会为您解决这个问题,请参阅编辑。
    • 谢谢您,先生!这完全有帮助!该工具有效!
    【解决方案5】:

    如果文件的格式定义正确,您可以尝试使用正则表达式的解决方案。 这里只是一个想法:

    import re
    
    fruits = {}
    with open('fruits_list.txt', mode='r') as f:
        for line in f:
            match = re.match("([a-zA-Z0-9]+)[\s]+([0-9]+).*", line)
            if match:
                fruit_name, fruit_amount = match.groups()
                fruits[fruit_name] = fruit_amount
    
    
    print fruits
    

    更新: 我改变了读取大文件的方式。现在我逐行阅读,而不是全部阅读。这提高了内存使用率。

    【讨论】:

    • 不是一个好主意。开头的文本中可能有特殊字符。即使不是这样,regex 通常也不是解决这些问题的最佳方法,因为在这种情况下它实际上比其他方法慢(与流行的观点相反)。对于给定的文本,使用此解决方案的 10k 循环平均需要 12.5 微秒,而使用我的解决方案(Python 生成器)则需要 8.5 微秒。对于更长的文本,差异会变得更加明显。有什么要考虑的吗?
    • 是的,我实际上是逐行计时的,因为我是在text.split('\n') 上完成的,'text' 是我从这里复制的字符串。所以我的抱怨仍然存在。尽管逐行阅读引入了另一个问题(我认为在这种情况下并不重要);这就是 Python 在 30 秒后自动关闭文件的事实。所以我想另一种处理大数据的解决方案是分块读取文件,同时将提取的数据写入 CSV 或 JSON 中。新文件会更小(没有 cmets 或空格),并且解析速度要快得多。只是一个想法!
    • @PouriaHadjibagheri 在哪里可以找到有关自动关闭问题的信息?当脚本在 30 秒内从文件中读取时,它仍然会发生吗?或仅在 30 秒后。不活动?在非常大的文件中按块读取听起来不错!
    • 我真的不记得我在哪里读到/遇到过。那是几年前的事了。如果长时间不使用会发生这种情况,那将更有意义。另一种可能性是我将它与不同的语言混合在一起!但无论如何,老实说,如果需要 30 秒来读取文件,最好重新考虑处理该文件的方法!
    • 当然,总是如此。无论如何,使用with 是在 Python 中处理文件的最佳实践。除非在处理块(惰性)时,在这种情况下,最好使用 yield file_obj.read(chunk_size) 在生成器函数内的循环中执行它,从另一个处理读取块的函数调用,返回它(因此它是垃圾收集),然后去下一个。
    猜你喜欢
    • 2015-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-29
    • 2011-02-21
    • 2015-09-15
    • 2022-06-29
    相关资源
    最近更新 更多