在Python中初始化/创建/填充一个字典的字典的字典

4 投票
3 回答
525 浏览
提问于 2025-04-17 17:30

我之前在Python中用过字典,但对Python还是比较陌生。这次我想用一个三层的字典,也就是字典里面还有字典,里面的字典里还有字典。我想在编程之前先确认一下。

我想把所有的数据都存储在这个三层的字典里,想知道用Python的方式初始化这个字典,然后再读取文件并写入这样的数据结构,应该怎么做比较好。

我想要的字典类型是这样的:

{'geneid':
{'transcript_id':
{col_name1:col_value1, col_name2:col_value2}
}
}

数据的类型是这样的:

geneid\ttx_id\tcolname1\tcolname2\n
hello\tNR432\t4.5\t6.7
bye\tNR439\t4.5\t6.7

有没有什么好的方法可以做到这一点呢?

谢谢!

3 个回答

2

我在研究中经常需要这样做。在编码时,你会想用defaultdict这个包,因为它可以让你通过简单的赋值在任何层级添加键值对。我会在回答你的问题后给你演示。这段内容直接来自我其中一个程序。重点关注最后四行(不是注释的部分),然后追踪变量在整个代码块中的变化,看看它在做什么:

from astropy.io import fits #this package handles the image data I work with
import numpy as np
import os
from collections import defaultdict

klist = ['hdr','F','Ferr','flag','lmda','sky','skyerr','tel','telerr','wco','lsf']
dtess = []

for file in os.listdir(os.getcwd()):
    if file.startswith("apVisit"):
        meff = fits.open(file, mode='readonly', ignore_missing_end=True)
        hdr = meff[0].header
        oid = str(hdr["OBJID"]) #object ID
        mjd = int(hdr["MJD5"].strip(' ')) #5-digit observation date
        for k,v in enumerate(klist):
            if k==0:
                dtess = dtess+[[oid,mjd,v,hdr]]
            else:
                dtess=dtess+[[oid,mjd,v,meff[k].data]]
        #header extension works differently from the rest of the image cube
        #it's not relevant to populating dictionaries
#HDUs in order of extension no.: header, flux, flux error, flag mask, 
# wavelength, sky flux, error in sky flux, telluric flux, telluric flux errors,
# wavelength solution coefficients, & line-spread function
dtree = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
for s,t,u,v in dtess:
    dtree[s][t][u].append(v)
#once you've added all the keys you want to your dictionary, 
#set default_factory attribute to None 
dtree.default_factory = None

下面是简化版。

  1. 首先,对于一个n层的字典,你需要把所有内容整理并放入一个(n+1)元组的列表中,格式是[key_1, key_2, ... , key_n, value]。
  2. 然后,要初始化这个n层字典,你只需要输入"defaultdict(lambda: "(去掉引号)n-1次,最后加上"defaultdict(list)"(或者其他数据类型),然后闭合括号。
  3. 用一个for循环来向列表中添加内容。*注意:当你想访问最底层的数据值时,可能需要输入my_dict[key_1][key_2][...][key_n][0],这样才能获取实际的值,而不仅仅是数据类型的描述。
  4. *编辑:当你的字典达到你想要的大小时,把default_factory属性设置为None。

如果你没有把default_factory设置为None,你可以通过输入类似my_dict[key_1][key_2][...][new_key]=new_value的方式,或者使用append()命令,来后续添加内容到你的嵌套字典中。只要你添加的字典不是嵌套的,你甚至可以添加更多的字典。

* 警告! 在代码片段的最后一行,你设置default_factory属性为None,这一步是超级重要的。你的电脑需要知道你什么时候完成了字典的添加,否则它可能会继续在后台分配内存,以防止出现缓冲区溢出,这会占用你的RAM,直到程序卡住。这是一种内存泄漏。我在写完这个答案后不久就吃了这个亏,这个问题困扰了我好几个月,我甚至觉得最后不是我自己发现的,因为我对内存分配一无所知。

2

我也在寻找其他的解决办法,结果在StackOverflow上找到了这个很不错的答案:

在Python中初始化一个字典的字典的最佳方法是什么?

简单来说,在我的情况下:

class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value
4

首先,我们来看看 csv 模块,它可以帮助我们处理解析行的工作:

import csv
with open('mydata.txt', 'rb') as f:
    for row in csv.DictReader(f, delimiter='\t'):
        print row

这段代码会输出:

{'geneid': 'hello', 'tx_id': 'NR432', 'col_name1': '4.5', 'col_name2': 6.7}
{'geneid': 'bye', 'tx_id': 'NR439', 'col_name1': '4.5', 'col_name2': 6.7}

现在,你只需要把这些数据重新整理成你想要的结构。这其实很简单,但有一点需要注意:第一次遇到某个 geneid 时,你需要为它创建一个新的空 dict,同样的,当你第一次在某个 geneid 下看到某个 tx_id 时,也需要这样做。你可以用 setdefault 来解决这个问题:

import csv
genes = {}
with open('mydata.txt', 'rb') as f:
    for row in csv.DictReader(f, delimiter='\t'):
        gene = genes.setdefault(row['geneid'], {})
        transcript = gene.setdefault(row['tx_id'], {})
        transcript['colname1'] = row['colname1']
        transcript['colname2'] = row['colname2']

你还可以用 defaultdict 让代码更易读:

import csv
from collections import defaultdict
from functools import partial
genes = defaultdict(partial(defaultdict, dict))
with open('mydata.txt', 'rb') as f:
    for row in csv.DictReader(f, delimiter='\t'):
        genes[row['geneid']][row['tx_id']]['colname1'] = row['colname1']
        genes[row['geneid']][row['tx_id']]['colname2'] = row['colname2']

这里的关键是,最外层的 dict 是一个特殊的字典,当它第一次看到一个新键时,会返回一个空的 dict……而这个返回的空 dict 本身也是一个空的 dict。唯一需要注意的地方是,defaultdict 需要一个函数,这个函数会返回合适类型的对象,而返回 defaultdict(dict) 的函数需要用 partiallambda 或者显式的函数来写。如果你想要更通用的版本,还有一些在 ActiveState 和 PyPI 上的模块可以创建新的字典,直到你需要的地方。

撰写回答