创建字典的字典的列表的Python习惯用法
给定这些数据:
foo kk type1 1 2 3
bar kk type2 3 5 1
我想创建一个字典,里面又包含字典和列表。
在Perl语言中,这种结构叫做“哈希的哈希的数组”。
可以用以下这行代码来实现(可以在这里运行:https://eval.in/118535)
push @{$hohoa{$name}{$type}},($v1,$v2,$v3);
在Perl中,$hohoa的输出结果是:
$VAR1 = {
'bar' => {
'type2' => [
'3',
'5',
'1'
]
},
'foo' => {
'type1' => [
'1',
'2',
'3'
]
}
};
在Python中怎么做呢?
更新:为什么下面这个for循环
的变体没有存储所有的值呢?
#!/usr/bin/env python
import sys
import pprint
from collections import defaultdict
outerdict = defaultdict(dict)
with open('data.txt') as infh:
for line in infh:
name, _, type_, values = line.split(None, 3)
valist = values.split();
for i in range(len(valist)):
thval = valist[i];
outerdict[name][type] = thval
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(outerdict)
它打印出这个:
defaultdict(<type 'dict'>, {'foo': {<type 'type'>: '3'}, 'bar': {<type 'type'>: '1'}})
更新2:当数据看起来像这样时,输出似乎有问题:
foo kk type1 1.2 2.10 3.3
bar kk type2 3.2 5.2 1.0
4 个回答
与其使用 defaultdict
,你可以用普通的 dict
,结合 reduce
和 dict.setdefault
来实现。下面是一个可以封装成函数的例子:
text_data = """foo kk type1 1 2 3
bar kk type2 3 5 1"""
data = [line.split() for line in text_data.splitlines()]
# [['foo', 'kk', 'type1', '1', '2', '3'], ['bar', 'kk', 'type2', '3', '5', '1']]
var1 = {}
for row in data:
# row[:2] everything before leaf, [2] is the leaf, row[3:] remainder of 'values'
reduce(lambda a,b: a.setdefault(b, {}), row[:2], var1)[2] = row[3:]
# {'foo': {'kk': {2: ['1', '2', '3']}}, 'bar': {'kk': {2: ['3', '5', '1']}}}
接下来,把它封装成一个函数,并可以选择性地为值添加转换器,比如:
def nested_dict(sequences, n, converter=lambda L: L):
ret = {}
for seq in sequences:
reduce(lambda a,b: a.setdefault(b, {}), seq[:n-1], ret)[n] = map(converter, seq[n:])
return ret
nested_dict(data, 2)
#{'foo': {2: ['type1', '1', '2', '3']}, 'bar': {2: ['type2', '3', '5', '1']}}
nested_dict(data, 3)
# {'foo': {'kk': {3: ['1', '2', '3']}}, 'bar': {'kk': {3: ['3', '5', '1']}}}
nested_dict(data, 3, int)
# {'foo': {'kk': {3: [1, 2, 3]}}, 'bar': {'kk': {3: [3, 5, 1]}}}
# ...
def make_strukture(lst_of_str):
result = {}
for i in my_strs:
data = i.split()
if data[0] in result.keys(): continue #Only one first key for foo, bar
result[data[0]] = {} #Create first key foo, bar-level
result[data[0]][data[2]] = list(data[3:]) #Skip kk and create second key with list
return result
#Below more comples data structure:
my_strs = ["foo kk type1 1 2 3", "foo kk type2 1 2 3", "bar kk type2 3 5 1"]
print make_strukture(my_strs)
{'foo':
{'type1': ['1', '2', '3']},
'bar':
{'type2': ['3', '5', '1']}
}
打印结果:
另一种很好的方法是这样做:
from collections import defaultdict
d = defaultdict(lambda: defaultdict(list))
# eg.
d["x"]["y"].append(100)
通过这种方式,你实际上是在创建一个字典,这个字典里面包含了很多其他字典,并且每个字典的默认值是一个列表。
这要看你想要实现什么;你需要在内部字典中添加多少个键呢?
最简单的方法就是直接为内部字典创建新的字典:
outerdict = {}
outerdict[name] = {type_: [v1, v2, v3]}
或者你可以使用 dict.setdefault()
来根据需要创建内部字典:
outerdict.setdefault(name, {})[type_] = [v1, v2, v3]
或者你可以使用 collections.defaultdict()
,这样可以自动处理新值:
from collections import defaultdict
outerdict = defaultdict(dict)
outerdict[name][type_] = [v1, v2, v3]
在逐行解析文件时,我会使用后者,虽然稍微简化了一下:
from collections import defaultdict
outerdict = defaultdict(dict)
with open(filename) as infh:
for line in infh:
name, _, type_, *values = line.split()
outerdict[name][type_] = [int(i) for i in values]
这段代码使用了 Python 3 的语法,将行中第一个 3 个值之后的剩余值捕获到 values
中。
如果是 Python 2 的版本会是:
with open(filename) as infh:
for line in infh:
name, _, type_, values = line.split(None, 3)
outerdict[name][type_] = map(int, values.split())
在这里,我将空格分割限制为 3 次(这样会得到 4 个值),然后再单独分割 values
字符串。
如果你想让最里面的列表累积所有重复的 (name, type_)
键组合的值,你需要使用稍微复杂一点的 defaultdict
设置;这个设置会生成一个内部的 defaultdict()
,用于生成 list
类型的值:
outerdict = defaultdict(lambda: defaultdict(list))
with open(filename) as infh:
for line in infh:
name, _, type_, values = line.split(None, 3)
outerdict[name][type_].extend(map(int, values.split()))
对于你实际发布的文件,我会采用 完全不同的方法:
import csv
from itertools import islice
outerdict = defaultdict(lambda: defaultdict(list))
with open('ImmgenCons_all_celltypes_MicroarrayExp.csv', 'rb') as infh:
reader = csv.reader(infh, skipinitialspace=True)
# first row contains metadata we need
celltypes = next(reader, [])[3:]
# next two rows can be skipped
next(islice(infh, 2, 2), None)
for row in reader:
name = row[1]
for celltype, value in zip(celltypes, row[3:]):
outerdict[name][celltype].append(float(value))