dict.fromkeys()和OrderedDict具体是如何实现去重的?

在 Python 中,dict.fromkeys()OrderedDict.fromkeys() 的去重逻辑核心依赖 "字典的键(key)必须唯一" 这一特性,本质是 "用键的唯一性过滤重复元素",仅在 "是否保留插入顺序" 上有差异(Python 3.7+ 后差异已极小)。以下从「原理拆解、底层实现、去重步骤」三方面详细说明:

一、核心前提:字典的键(key)特性

无论是普通 dict 还是 OrderedDict,都遵循两个关键规则:

  1. 键的唯一性 :字典中不能存在重复的 key,若重复赋值(如 d = {1: None, 1: None}),后定义的 key 会覆盖前一个(最终仅保留 1 个);

  2. 插入顺序保留(Python 3.7+):

    • 普通 dict 从 Python 3.7 开始,官方保证保留键的插入顺序(底层实现优化);
    • OrderedDict 是 Python 2.7+ 就存在的有序字典,专门设计用于 "严格保留插入顺序"(Python 3.7+ 后与普通 dict 的顺序特性一致,但仍有额外功能如 move_to_end())。

这两个规则是 "去重并保留顺序" 的核心基础。

二、dict.fromkeys() 的去重原理与实现

1. 函数功能与语法

dict.fromkeys(iterable, value=None) 是字典的类方法,作用是:

  • 接收一个可迭代对象 (如列表、元组),将其每个元素作为字典的 key
  • 所有 key 对应的 value 默认为 None(可通过第二个参数指定统一值);
  • 返回一个新字典。

语法示例:

python

运行

ini 复制代码
lst = [1, 2, 2, 3, 3, 3]
d = dict.fromkeys(lst)
print(d)  # 输出:{1: None, 2: None, 3: None}(去重且保留插入顺序)

2. 去重的底层实现步骤

dict.fromkeys() 本质是 "遍历可迭代对象,逐个往字典中添加 key",去重逻辑隐含在遍历过程中,具体步骤:

  1. 初始化空字典:创建一个空的哈希表(字典底层数据结构),用于存储 key-value 对;

  2. 遍历可迭代对象 :按顺序取出 iterable 中的每个元素(如列表 [1,2,2,3] 依次取 1、2、2、3);

  3. 判断 key 是否已存在

    • 对每个元素,计算其哈希值(hash(key)),通过哈希表快速查找是否已存在该 key(查找效率 O (1));
    • 若 key 不存在:将其插入字典(保留当前顺序),value 设为默认值 None
    • 若 key 已存在:直接跳过,不重复插入(这就是去重的核心);
  4. 返回字典:遍历结束后,字典中的 key 就是 "去重后且保留原插入顺序" 的结果。

3. 去重示例拆解(对应方式 1 的代码)

方式 1 中 filtered = [1,4,6,7,7](筛选后未去重的列表),执行 dict.fromkeys(filtered)

python

运行

ini 复制代码
filtered = [1,4,6,7,7]
d = dict.fromkeys(filtered)
  • 遍历第 1 个元素 1:哈希表中无 1,插入 → 字典:{1: None}
  • 遍历第 2 个元素 4:哈希表中无 4,插入 → 字典:{1: None, 4: None}
  • 遍历第 3 个元素 6:哈希表中无 6,插入 → 字典:{1: None, 4: None, 6: None}
  • 遍历第 4 个元素 7:哈希表中无 7,插入 → 字典:{1: None, 4: None, 6: None, 7: None}
  • 遍历第 5 个元素 7:哈希表中已存在 7,跳过 → 字典无变化;
  • 最终字典的 key 为 [1,4,6,7],转列表后就是去重结果。

4. 关键特点

  • 去重逻辑:依赖 "key 唯一",重复元素直接跳过,无显式比较(效率高);
  • 顺序保留:Python 3.7+ 保证插入顺序,与原列表顺序一致;
  • 效率:遍历 + 哈希查找,时间复杂度 O (m)(m 为 filtered 列表长度)。

三、OrderedDict.fromkeys() 的去重原理与实现

1. 与普通 dict.fromkeys() 的区别

OrderedDictcollections 模块中的有序字典,其 fromkeys() 方法功能、语法、去重逻辑与普通 dict.fromkeys() 完全一致,唯一差异是:

  • 顺序保证的兼容性 :Python 3.7 之前,普通 dict 不保证插入顺序,而 OrderedDict 从诞生起就严格保留插入顺序;
  • 额外功能OrderedDict 提供 move_to_end()popitem(last=True/False) 等顺序相关方法(去重场景用不到)。

2. 去重实现步骤

与普通 dict.fromkeys() 完全相同,仅底层存储结构略有差异(OrderedDict 内部用 "双向链表" 记录插入顺序,而 Python 3.7+ 普通 dict 用 "插入顺序数组" 记录),但对用户而言,去重效果一致:

python

运行

scss 复制代码
from collections import OrderedDict

filtered = [1,4,6,7,7]
od = OrderedDict.fromkeys(filtered)
print(od)  # 输出:OrderedDict([(1, None), (4, None), (6, None), (7, None)])
print(list(od.keys()))  # 输出:[1,4,6,7](去重且保留顺序)

3. 适用场景

  • Python 3.6 及以下版本:需 "去重 + 保留顺序" 时,必须用 OrderedDict.fromkeys()(普通 dict 无序);
  • Python 3.7 及以上版本:普通 dict.fromkeys() 已能满足 "去重 + 保留顺序",无需再用 OrderedDict(除非需要其额外的顺序操作方法)。

四、两者对比与总结

特性 dict.fromkeys() OrderedDict.fromkeys()
去重原理 依赖 key 唯一性,重复 key 跳过 依赖 key 唯一性,重复 key 跳过
顺序保留(Python 3.7+)
顺序保留(Python <3.7)
底层效率 略高(结构更简单) 略低(双向链表额外开销)
依赖 无(内置 dict) 需导入 collections.OrderedDict
适用场景 Python 3.7+ 日常去重 + 保序 Python <3.7 或需额外顺序操作的场景

五、关键补充:为什么不用 set 去重?

可能有疑问:"既然 set 也能去重,为什么还要用 dict.fromkeys()?"------ 核心差异是「是否保留顺序」:

  • set 是无序结构,去重后会打乱原列表顺序(如 list(set([1,4,6,7,7])) 可能输出 [1,6,4,7]);
  • dict.fromkeys()OrderedDict.fromkeys() 去重后严格保留原列表的插入顺序(这是方式 1 的核心需求)。

例如:

python

运行

less 复制代码
filtered = [4,1,6,7,7]  # 原顺序是 4→1→6→7
print(list(set(filtered)))  # 输出:[1,4,6,7](顺序打乱)
print(list(dict.fromkeys(filtered).keys()))  # 输出:[4,1,6,7](顺序保留)

最终总结

dict.fromkeys()OrderedDict.fromkeys() 的去重逻辑本质是:

  1. 利用 "字典 key 不可重复" 的特性,遍历元素时跳过已存在的 key;
  2. 保留元素的 "首次插入顺序"(Python 3.7+ 普通 dict 也支持);
  3. 去重效率高(O (m)),且能兼顾顺序,是 "去重 + 保序" 的最优方案之一。

方式 1 中用 dict.fromkeys(),正是因为它能高效完成 "筛选后列表的去重 + 保序",且语法简洁、无需额外依赖

相关推荐
e***98572 小时前
Springboot的jak安装与配置教程
java·spring boot·后端
干饭比赛第一名获得者2 小时前
🚀 终极指南:Mac M4 编译 Rust 至 Linux (AMD64)
后端·rust
骑着bug的coder2 小时前
以为是 MyBatis Plus 链式调用的“优雅”写法,结果反手给我报了个 NPE?
后端
w***4242 小时前
SpringSecurity的配置
android·前端·后端
皖南大花猪2 小时前
Go 项目中使用 Casbin 实现 RBAC 权限管理完整教程
开发语言·后端·golang·rbac·casbin
源代码•宸2 小时前
GoLang写一个火星漫游行动
开发语言·经验分享·后端·golang
i***13242 小时前
【SpringBoot】单元测试实战演示及心得分享
spring boot·后端·单元测试
s***35302 小时前
SpringMVC新版本踩坑[已解决]
android·前端·后端
D***44142 小时前
【SpringBoot】Spring Boot 项目的打包配置
java·spring boot·后端