如何优雅的生成及遍历python嵌套字典

本文介绍Python中创建和遍历嵌套字典的两种方法:使用自定义类Vividict和defaultdict。Vividict通过__missing__方法处理未定义的键,defaultdict则提供默认值。同时,文章提供了遍历嵌套字典的有效策略。

嵌套字典生成

方法一:定义类

 



 

class Vividict(dict):

def __missing__(self, key):

value = self[key] = type(self)()

return value

解释:

  • 第一行:class后面紧接着是类名,即Vividict,类名通常是大写开头的单词,紧接着是(dict),表示该类是dict类继承下来的。

我们可以使用dir(dict)查看dict的方法


 

In[22]: print(dir(dict))


 

['__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

同理,可以查看Vividict的方法


 

In[23]: print(dir(Vividict))


 

['__class__', '__contains__', '__delattr__', '__delitem__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__missing__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'clear', 'copy', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

比较两者可以发现,Vividict的方法比dict的方法多了一个missing方法,也就是我们添加的方法。所以这就是继承,继承最大的好处是子类获得了父类的全部功能,而不必重新造轮子。

  • 第二行:python魔法方法中的自定义序列,类似于定义一个函数。missing在字典的子类中使用,它定义了当试图访问一个字典中不存在的键时的行为(目前为止是指字典的实例,例如我有一个字典 d , “george” 不是字典中的一个键,当试图访问 d[‘george’] 时就会调用 d.missing(“george”),结果为{} )。

  • 第三行,第四行:访问字典中不存在的键(key)时,返回空字典作为其返回值(value)

例如:


 

In[17]: a = dict()


 

In[18]: type(a)()

Out[18]:

{}

注意:

  • 特殊方法“missing”前后有两个下划线!!!
  • 和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

使用:


 



 

# coding=utf-8

#导入模块

import os, openpyxl

import pprint

from pandas import DataFrame

#pprint模块可以输出漂亮的字典结构,但是不利于后期利用R作图

#DataFrame可以将字典结构转为数据框输出,方便后期利用R作图

#切换工作路径

os.chdir(r'F:\pycharm_project\mutation_count')

#读取excel表格

wb = openpyxl.load_workbook('东方肝胆数据综合.xlsx')

sheet = wb.active

#定义类

class Vividict(dict):

def __missing__(self, key):

value = self[key] = type(self)()

return value

#实例化

d = Vividict()

#字典初始化,赋初值0

for i in range(2,sheet.max_row+1):

d[sheet.cell(row=i, column=1).value][sheet.cell(row=i, column=15).value] = 0

#累加统计各个样本各种突变类型的数目

for i in range(2,sheet.max_row+1):

d[sheet.cell(row=i, column=1).value][sheet.cell(row=i, column=15).value] +=1

pprint.pprint(d)

#输出字典结构

pprint.pprint(d)

{'PDC1279A_vs_PDC1279': {'UTR3': 9,

'UTR5': 4,

'downstream': 5,

'exonic': 149,

'intergenic': 170,

'intronic': 163,

'ncRNA_exonic': 17,

'ncRNA_intronic': 23,

'splicing': 2,

'upstream;downstream': 2},

'PDC1279C_vs_PDC1279': {'UTR3': 11,

'UTR5': 13,

'downstream': 1,

'exonic': 174,

'intergenic': 189,

'intronic': 172,

'ncRNA_exonic': 24,

'ncRNA_intronic': 36,

'splicing': 4,

'upstream': 2,

'upstream;downstream': 2}}

#输出数据框结构,缺损的元素用 NaN补齐

frame = DataFrame(d)

print(frame)

PDC1279A_vs_PDC1279 PDC1279C_vs_PDC1279 \

UTR3 9.0 11.0

UTR5 4.0 13.0

downstream 5.0 1.0

exonic 149.0 174.0

exonic;splicing NaN NaN

intergenic 170.0 189.0

intronic 163.0 172.0

ncRNA_exonic 17.0 24.0

ncRNA_intronic 23.0 36.0

ncRNA_splicing NaN NaN

splicing 2.0 4.0

upstream NaN 2.0

upstream;downstream 2.0 2.0

方法二:使用defaultdict()

两个维度字典:


 



 

from collections import defaultdict

d = defaultdict(defaultdict)

d[1][2] = 3

等价于:


 



 

from collections import defaultdict

def nested_dict_factory():

return defaultdict(int)

def nested_dict_factory2():

return defaultdict(nested_dict_factory)

db = defaultdict(nested_dict_factory2)

当然,第一种方法简洁的多!

要获得更多维度,你可以(三维):


 



 

from collections import defaultdict

d = defaultdict(lambda :defaultdict(defaultdict))

d[1][2][3] = 4

使用defaultdict任何未定义的key都会默认返回一个根据method_factory参数不同的默认值, 而相同情况下dict()会返回KeyError.

python中lambda存在意义就是对简单函数的简洁表示

实际上 defaultdict也是通过missing方法实现的。defaultdict在dict的基础上添加了一个missing(key)方法, 在调用一个不存的key的时候, defaultdict会调用missing, 返回一个根据default_factory参数的默认值, 所以不会返回Keyerror.


 



 

In[35]: print(dir(defaultdict))

['__class__', '__contains__', '__copy__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__missing__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'clear', 'copy', 'default_factory', 'fromkeys', 'get', 'items', 'keys', 'pop', 'popitem', 'setdefault', 'update', 'values']

嵌套字典的遍历

方法一:一层一层的嵌套迭代,从而实现遍历

 



 

for key,value in d.items():

for key2, val2 in value.items():

print (key2, val2)

在类中定义walk方法实现嵌套字典的遍历


 



 

class Vividict(dict):

def __missing__(self, key):

value = self[key] = type(self)()

return value

def walk(self):

for key, value in self.items():

if isinstance(value, Vividict):

for tup in value.walk():

yield (key,) + tup

else:

yield key, value

解释:
第1-4行:上面已经解释过了
第5-11行:定义一个walk函数,并对字典items对象的key和value进行遍历,isinstance用于判断对象类型,如果value是一个字典,那么对value调用walk()方法继续进行遍历,一层一层将key,value存储在元祖中()。当最里面一层,即else情况,输出key,value。整个过程即将字典数据结构扁平化为元祖

此时,我们可以这样来遍历字典(输出元祖)


 

#打印整个元祖


 

for tup in d.walk():

print(tup)

('PDC1279_vs_PDC1279C6', 'downstream', 3)

('PDC1279_vs_PDC1279C6', 'UTR3', 11)

('PDC1279_vs_PDC1279C6', 'intronic', 164)

('PDC1279_vs_PDC1279C6', 'splicing', 4)

**这就是扁平化的字典**

#打印元祖的第3列

for tup in d.walk():

print(tup[2])

参考

(1)https://ask.helplib.com/229754

(2)Python魔法方法指南(推荐阅读)

http://pyzh.readthedocs.io/en/latest/python-magic-methods-guide.html

转载于:https://www.cnblogs.com/Javauser/p/10682726.html

<think>我们面对的是一个多重嵌套字典遍历问题。根据引用[1]和引用[2]提供的思路,我们可以采用递归的方法来遍历嵌套字典。同时,我们要注意,在遍历过程中可能需要处理字典、列表等多种嵌套结构。 目标:遍历一个多重嵌套字典,访问所有的键值对(包括嵌套在内部字典或列表中的值) 思路: 1. 设计一个递归函数,该函数能够处理字典、列表以及其他类型的元素。 2. 当遇到字典时,递归遍历字典的每一个键值对。 3. 当遇到列表(或其他可迭代的非字典类型)时,递归遍历其中的每一个元素(但注意,我们可能只关心字典,或者需要继续深入列表中的字典)。 4. 对于非字典且非列表的元素,我们可以直接处理(例如打印或收集)。 但是,根据引用[2]中的示例,他们只递归处理了字典,而将非字典的值收集到一个列表中。我们的需求是遍历并访问所有值,所以我们可以选择打印路径和值,或者收集所有值。 下面我们提供两种方式: 方式一:递归打印所有键值对(包括嵌套的) 方式二:将嵌套字典扁平化到一个列表中(如引用[2]的做法) 但是,用户要求的是遍历,所以我们更倾向于方式一,即访问并打印每个键值对(或者根据用户需求执行其他操作)。 另外,我们还需要考虑如何表示键的路径(因为嵌套,所以键可能重复,需要路径来区分)。 因此,我们可以设计一个递归函数,它接受当前要遍历数据结构,以及当前的路径(用于记录从根到当前节点的键序列)。 步骤: 1. 如果当前节点是字典,则遍历这个字典的每一个键值对,对于每个键值对,将当前键添加到路径中,然后递归处理值。 2. 如果当前节点是列表(或元组),则遍历列表的每一个元素,将索引作为路径的一部分(或者用特殊标记),然后递归处理每个元素。 3. 如果当前节点是其他类型,则输出当前路径和对应的值(或者进行其他操作)。 注意:为了避免循环引用导致的无限递归,我们应该考虑处理循环引用的情况(但一般字典不会出现,除非是特殊构造的循环引用,这里我们暂时不考虑,因为用户问题没有提到)。 下面我们实现一个递归遍历函数,并打印出每个叶子节点的路径和值。 示例字典: ```python nested_dict = { 'a': 1, 'b': { 'c': 2, 'd': [3, 4, {'e': 5}] } } ``` 我们期望的遍历输出(一种可能的格式): a: 1 b.c: 2 b.d[0]: 3 b.d[1]: 4 b.d[2].e: 5 实现: 我们可以这样设计递归函数: traverse(data, path=None, separator='.') path: 当前路径(字符串或列表) separator: 路径连接符 对于字典,我们将键连接到路径上,然后递归值。 对于列表,我们将索引用方括号括起来连接到路径上,然后递归每个元素。 注意:由于列表可能嵌套,所以我们需要处理列表中的字典。 代码实现如下:</think>在 Python遍历多重嵌套字典,核心是使用递归方法处理不同层级的嵌套结构。以下是完整解决方案: ### 递归遍历多重嵌套字典 ```python def traverse_nested_dict(data, path=None, result=None): """ 递归遍历多重嵌套字典 :param data: 当前处理的字典或值 :param path: 当前路径(用于记录键的层级关系) :param result: 存储所有键值对的列表 :return: 包含所有路径和值的列表 """ if path is None: path = [] if result is None: result = [] # 如果是字典类型 if isinstance(data, dict): for key, value in data.items(): new_path = path + [key] # 更新当前路径 traverse_nested_dict(value, new_path, result) # 递归处理值 # 如果是列表或元组类型 elif isinstance(data, (list, tuple)): for idx, item in enumerate(data): new_path = path + [f"[{idx}]"] # 添加索引标记 traverse_nested_dict(item, new_path, result) # 递归处理元素 # 基础数据类型(终止递归) else: full_path = ".".join(str(p) for p in path) # 生成完整路径 result.append((full_path, data)) # 存储路径和值 return result # 使用示例 nested_dict = { "user": { "name": "小明", "scores": [85, 92, 78], "contact": { "email": "xiaoming@example.com", "phones": ["13800138000", "13900139000"] } }, "system": { "version": 2.1, "config": {"dark_mode": True} } } # 遍历并打印结果 for path, value in traverse_nested_dict(nested_dict): print(f"{path} => {value}") ``` ### 输出结果示例 ``` user.name => 小明 user.scores[0] => 85 user.scores[1] => 92 user.scores[2] => 78 user.contact.email => xiaoming@example.com user.contact.phones[0] => 13800138000 user.contact.phones[1] => 13900139000 system.version => 2.1 system.config.dark_mode => True ``` ### 关键处理逻辑 1. **递归处理字典**: ```python if isinstance(data, dict): for key, value in data.items(): new_path = path + [key] traverse_nested_dict(value, new_path, result) ``` 2. **处理列表/元组嵌套**: ```python elif isinstance(data, (list, tuple)): for idx, item in enumerate(data): new_path = path + [f"[{idx}]"] # 添加索引标识 traverse_nested_dict(item, new_path, result) ``` 3. **终止递归条件**: ```python else: # 基础数据类型 full_path = ".".join(str(p) for p in path) result.append((full_path, value)) ``` ### 特殊场景处理 #### 1. 仅收集特定路径的值 ```python def find_values_by_key(data, target_key): """收集特定键的所有值""" results = [] if isinstance(data, dict): for key, value in data.items(): if key == target_key: results.append(value) elif isinstance(value, (dict, list)): results.extend(find_values_by_key(value, target_key)) elif isinstance(data, list): for item in data: results.extend(find_values_by_key(item, target_key)) return results # 查找所有email值 emails = find_values_by_key(nested_dict, "email") ``` #### 2. 修改嵌套值(不改变结构) ```python def modify_nested_values(data, modifier): """递归修改所有值但不改变键结构""" if isinstance(data, dict): return {k: modify_nested_values(v, modifier) for k, v in data.items()} elif isinstance(data, list): return [modify_nested_values(item, modifier) for item in data] else: return modifier(data) # 应用修改函数 # 将所有数值乘以2 modified_dict = modify_nested_values(nested_dict, lambda x: x*2 if isinstance(x, (int, float)) else x) ``` ### 性能优化建议 1. **避免深层递归**: ```python # 使用迭代代替递归(防止栈溢出) def iterative_traversal(data): stack = [(data, [])] results = [] while stack: current, path = stack.pop() if isinstance(current, dict): for k, v in current.items(): stack.append((v, path + [k])) elif isinstance(current, list): for i, item in enumerate(current): stack.append((item, path + [f"[{i}]"])) else: full_path = ".".join(str(p) for p in path) results.append((full_path, current)) return results ``` 2. **使用生成器节省内存**: ```python def traverse_generator(data, path=None): if path is None: path = [] if isinstance(data, dict): for k, v in data.items(): yield from traverse_generator(v, path + [k]) elif isinstance(data, list): for i, item in enumerate(data): yield from traverse_generator(item, path + [f"[{i}]"]) else: yield (".".join(str(p) for p in path), data) # 使用示例 for path, value in traverse_generator(nested_dict): print(path, "=>", value) ``` ### 注意事项 1. **循环引用处理**:当数据结构存在循环引用时,需添加已访问记录防止无限递归 ```python def safe_traversal(data, path=None, visited=None): if visited is None: visited = set() obj_id = id(data) if obj_id in visited: return visited.add(obj_id) # ... 其余递归逻辑相同 ... ``` 2. **类型处理扩展**:根据需要可扩展支持更多数据类型(如集合、自定义对象等)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值