在Python中初始化/创建/填充一个字典的字典的字典
我之前在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 个回答
我在研究中经常需要这样做。在编码时,你会想用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
下面是简化版。
- 首先,对于一个n层的字典,你需要把所有内容整理并放入一个(n+1)元组的列表中,格式是[key_1, key_2, ... , key_n, value]。
- 然后,要初始化这个n层字典,你只需要输入"defaultdict(lambda: "(去掉引号)n-1次,最后加上"defaultdict(list)"(或者其他数据类型),然后闭合括号。
- 用一个for循环来向列表中添加内容。*注意:当你想访问最底层的数据值时,可能需要输入my_dict[key_1][key_2][...][key_n][0],这样才能获取实际的值,而不仅仅是数据类型的描述。
- *编辑:当你的字典达到你想要的大小时,把default_factory属性设置为None。
如果你没有把default_factory设置为None,你可以通过输入类似my_dict[key_1][key_2][...][new_key]=new_value的方式,或者使用append()命令,来后续添加内容到你的嵌套字典中。只要你添加的字典不是嵌套的,你甚至可以添加更多的字典。
* 警告! 在代码片段的最后一行,你设置default_factory属性为None,这一步是超级重要的。你的电脑需要知道你什么时候完成了字典的添加,否则它可能会继续在后台分配内存,以防止出现缓冲区溢出,这会占用你的RAM,直到程序卡住。这是一种内存泄漏。我在写完这个答案后不久就吃了这个亏,这个问题困扰了我好几个月,我甚至觉得最后不是我自己发现的,因为我对内存分配一无所知。
我也在寻找其他的解决办法,结果在StackOverflow上找到了这个很不错的答案:
简单来说,在我的情况下:
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
首先,我们来看看 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)
的函数需要用 partial
、lambda
或者显式的函数来写。如果你想要更通用的版本,还有一些在 ActiveState 和 PyPI 上的模块可以创建新的字典,直到你需要的地方。