在Python中将文件解析为字典

3
我有一个文件,其中一小部分如下所示:

我有一个文件,其中一小部分如下所示:

Clutch001
Albino X Pastel
Bumble Bee X Albino Lesser
Clutch002
Bee X Fire Bee
Albino Cinnamon X Albino
Mojave X Bumble Bee
Clutch003
Black Pastel X Banana Ghost Lesser
....

ClucthXXX和下一个ClutchXXX之间的字符串数量可能不同,但不为零。 我想知道是否有办法使用特定字符串作为键(在我的情况下是ClutchXXX),并将文本作为字典的值,直到第二次出现特定字符串? 我希望获得这样的字典:

d={'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser'
   'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee'
   'Clutch003': 'Black Pastel X Banana Ghost Lesser'}

我主要关注的是将字符串模式作为键保存,然后以其后的文本作为值。如果您有任何建议或指导可行的方法,将不胜感激。


离合器编号是否总是单独一行? - Jonathan Leffler
@JonathanLeffler 是的,它总是独立的。 - midori
“Clutch”这个词会出现在其他的行里吗?如果不会,你可以使用 .split('Clutch') - Stuart
但是它后面有一个数字,能否在分割时使用正则表达式? - midori
只要关键词(“Clutch”)的字母部分不在其他地方出现,就不需要使用正则表达式。请看下面的回答。 - Stuart
7个回答

4
from itertools import groupby
from functools import partial

key = partial(re.match, r'Clutch\d\d\d')

with open('foo.txt') as f:
    groups = (', '.join(map(str.strip, g)) for k, g in groupby(f, key=key))
    pprint(dict(zip(*[iter(groups)]*2)))

{'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser',
 'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee',
 'Clutch003': 'Black Pastel X Banana Ghost Lesser'}

3

将行收集到列表中,同时将该列表存储在字典中:

d = {}
values = None
with open(filename) as inputfile:
    for line in inputfile:
        line = line.strip()
        if line.startswith('Clutch'):
            values = d[line] = []
        else:
            values.append(line)

这将给你带来:
{'Clutch001': ['Albino X Pastel', 'Bumble Bee X Albino Lesser']
 'Clutch002': ['Bee X Fire Bee', 'Albino Cinnamon X Albino', 'Mojave X Bumble Bee']
 'Clutch003': ['Black Pastel X Banana Ghost Lesser']}

在加载文件后,将所有这些列表转化为单个字符串非常容易:

d = {key: ', '.join(value) for key, value in d.items()}

您也可以在读取文件时进行连接; 我会使用生成器函数以组的形式处理文件:

def per_clutch(inputfile):
    clutch = None
    lines = []
    for line in inputfile:
        line = line.strip()
        if line.startswith('Clutch'):
            if lines:
                yield clutch, lines
            clutch, lines = line, []
        else:
            lines.append(line)
    if clutch and lines:
        yield clutch, lines

然后将所有组都放入一个字典中:

with open(filename) as inputfile:
    d = {clutch: ', '.join(lines) for clutch, lines in per_clutch(inputfile)}

演示后者的例子:
>>> def per_clutch(inputfile):
...     clutch = None
...     lines = []
...     for line in inputfile:
...         line = line.strip()
...         if line.startswith('Clutch'):
...             if lines:
...                 yield clutch, lines
...             clutch, lines = line, []
...         else:
...             lines.append(line)
...     if clutch and lines:
...         yield clutch, lines
... 
>>> sample = '''\
... Clutch001
... Albino X Pastel
... Bumble Bee X Albino Lesser
... Clutch002
... Bee X Fire Bee
... Albino Cinnamon X Albino
... Mojave X Bumble Bee
... Clutch003
... Black Pastel X Banana Ghost Lesser
... '''.splitlines(True)
>>> {clutch: ', '.join(lines) for clutch, lines in per_clutch(sample)}
{'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser', 'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee', 'Clutch003': 'Black Pastel X Banana Ghost Lesser'}
>>> from pprint import pprint
>>> pprint(_)
{'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser',
 'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee',
 'Clutch003': 'Black Pastel X Banana Ghost Lesser'}

@BallPython:不需要额外的迭代器,直接对文件进行迭代就可以了;这样会按需读取行。 - Martijn Pieters
值(values)是一个空类型(none type)对象,因此它没有附加属性(append attribute)。 - midori
@BallPython:那么你的第一行不是以“Clutch”开头;只有当遇到以“Clutch”开头的行时,values才被设置为一个列表。 - Martijn Pieters
加一,values = d[line] = [] 真是太棒了。我会使用你的第一个代码,但其他方法有什么用呢?它们不如第一个方法简单。 - jamylak

2

如评论中所述,如果“Clutch”(或任何关键字)不会出现在非关键字行中,您可以使用以下内容:

keyword = "Clutch"
with open(filename) as inputfile:
    t = inputfile.read()
    d = {keyword + s[:3]: s[3:].strip().replace('\n', ', ') for s in t.split(keyword)}

如果您的文件可能会非常大,那么应该避免使用此方法,因为它会一次性将整个文件读入内存中。


2
您可以使用re.split()来枚举文件中的"Clutch"部分:
import re

tokens = iter(re.split(r'(^Clutch\d{3}\s*$)\s+', file.read(), flags=re.M))
next(tokens) # skip until the first Clutch
print({k: ', '.join(v.splitlines()) for k, v in zip(tokens, tokens)})

输出

{'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser', 
 'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee',
 'Clutch003': 'Black Pastel X Banana Ghost Lesser'}

2

让我们来看一下 'file.txt' 文件的内容:

Clutch001
白化X巴斯特
大黄蜂X白化巴斯特
Clutch002
蜜蜂X火蜜蜂
白化肉桂X白化
Mojave X 大黄蜂
Clutch003
黑色巴斯特X香蕉幽灵巴斯特

要获取您的字典,请尝试以下操作:

import re

with open('file.txt', 'r') as f:
    result = re.split(
        r'(Clutch\d{3}).*?',
        f.read(),
        flags=re.DOTALL # including '\n'
    )[1:] # result is ['Clutch001', '\nAlbino X Pastel\nBumble Bee X Albino Lesser\n', 'Clutch002', '\nBee X Fire Bee\nAlbino Cinnamon X Albino\nMojave X Bumble Bee\n', 'Clutch003', '\nBlack Pastel X Banana Ghost Lesser\n']

    keys = result[::2] # keys is ['Clutch001', 'Clutch002', 'Clutch003']
    values = result[1::2] # values is ['\nAlbino X Pastel\nBumble Bee X Albino Lesser\n', '\nBee X Fire Bee\nAlbino Cinnamon X Albino\nMojave X Bumble Bee\n', '\nBlack Pastel X Banana Ghost Lesser\n']

    values = map(
        lambda value: value.strip().replace('\n', ', '),
        values
    ) # values is ['Albino X Pastel, Bumble Bee X Albino Lesser', 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee', 'Black Pastel X Banana Ghost Lesser']

    d = dict(zip(keys, values)) # d is {'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser', 'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee', 'Clutch003': 'Black Pastel X Banana Ghost Lesser'}

1
这是一个基本可用的版本。我不确定它是否符合Python风格(可能可以压缩,肯定可以改进):
import re
import fileinput

d = dict()
key = ''
rx = re.compile('^Clutch\d\d\d$')

for line in fileinput.input():
    line = line[0:-1]
    if rx.match(line):
        key = line
        d[key] = ''
    else:
        d[key] += line

print d

for key in d:
    print key, d[key]

输出结果(重复信息)为:

{'Clutch001': 'Albino X PastelBumble Bee X Albino Lesser', 'Clutch002': 'Bee X Fire BeeAlbino Cinnamon X AlbinoMojave X Bumble Bee', 'Clutch003': 'Black Pastel X Banana Ghost Lesser'}
Clutch001 Albino X PastelBumble Bee X Albino Lesser
Clutch002 Bee X Fire BeeAlbino Cinnamon X AlbinoMojave X Bumble Bee
Clutch003 Black Pastel X Banana Ghost Lesser

如果由于某种原因第一行不是“离合器”行,则由于空键而出现错误。
用逗号连接,在处理破损的文本文件(末尾没有换行符)等方面:
import fileinput

d = {}

for line in fileinput.input():
    line = line.rstrip('\r\n') # line.strip() for leading and trailing space
    if line.startswith('Clutch'):
        key = line
        d[key] = ''
        pad = ''
    else:
        d[key] += pad + line
        pad = ', '

print d

for key in d:
    print "'%s': '%s'" % (key, d[key])

“pad”技术在其他情况下很实用,而且在这里也可以正常使用。虽然我相信它不会被认为是Pythonic的。
修改后的样本输出:
{'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser', 'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee', 'Clutch003': 'Black Pastel X Banana Ghost Lesser'}
'Clutch001': 'Albino X Pastel, Bumble Bee X Albino Lesser'
'Clutch002': 'Bee X Fire Bee, Albino Cinnamon X Albino, Mojave X Bumble Bee'
'Clutch003': 'Black Pastel X Banana Ghost Lesser'

这不会使用逗号将字符串连接起来。 - Martijn Pieters
此外,你真的应该使用 str.strip()str.rstrip() 来从行中删除空格(最后一行并不总是有一个换行符,所以你的 line[0:-1] 会删除错误的字符,或者如果文件在 Windows 的本地换行模式下打开,你会得到一个尾随的 \r)。 - Martijn Pieters
str.strip()函数可以去除字符串开头和结尾的空格(默认情况下),但不确定是否适用于所有情况。从技术上讲,文本文件必须以换行符结束,但是我知道有些粗心的编辑器(无论是人还是程序)可能会在文本文件末尾缺少换行符。我猜这种问题在Windows系统上比Unix更为常见。我正在处理连接方面的问题。 - Jonathan Leffler
示例开头没有空格;我还提到了 str.rstrip()。如果您想要非常精确地去除行末的换行符和回车符,请使用 line.rstrip('\r\n'),并且其他空格必须保留。然而,这种用法相当罕见。 - Martijn Pieters
目前来看,Martin的更适合您所述的要求。如果您的要求更加灵活,那么我的可能更简单。即便如此,还有改进的空间。 - Jonathan Leffler

1
假设单词 Clutch 独立成行,以下方法可行:
import re
d = {}
with open(filename) as f:
for line in f:
    if re.match("^Clutch[0-9]+", line) :
        match = line   # match is the key searched for
        match = match.replace('\n', ' ')    # newlines are replaced
        d[match] = ''
    else:
        line = line.replace('\n', ' ')
        d[match] += line  # all lines without the word 'Clutch'
                          # are added to the matched key

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接