创建字典的字典的列表的Python习惯用法

4 投票
4 回答
7086 浏览
提问于 2025-04-17 21:54

给定这些数据:

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 个回答

0

与其使用 defaultdict,你可以用普通的 dict,结合 reducedict.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]}}}
# ...
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']}
}

打印结果:

3

另一种很好的方法是这样做:

from collections import defaultdict

d = defaultdict(lambda: defaultdict(list))

# eg.
d["x"]["y"].append(100)

通过这种方式,你实际上是在创建一个字典,这个字典里面包含了很多其他字典,并且每个字典的默认值是一个列表。

7

这要看你想要实现什么;你需要在内部字典中添加多少个键呢?

最简单的方法就是直接为内部字典创建新的字典:

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))

撰写回答