Python嵌套字典的引用陷阱与解决方案:避免所有键指向同一值

Python嵌套字典的引用陷阱与解决方案:避免所有键指向同一值

本文深入探讨了python中在创建嵌套字典时,由于对象引用特性可能导致所有外层字典键最终指向同一个内层字典实例的问题。通过具体代码示例,详细阐述了这一陷阱的成因,并提供了两种有效的解决方案:使用 `dict.copy()` 方法进行浅拷贝,以及在循环内部重新初始化内层字典,以确保每个外层键都拥有独立的内层字典副本。

引言:Python字典的引用行为

在Python中,变量赋值并非复制值本身,而是复制对内存中对象的引用。对于不可变对象(如数字、字符串、元组),这通常不会引起混淆,因为一旦创建,它们的值就不能改变。然而,对于可变对象(如列表、字典、集合),当多个变量引用同一个可变对象时,通过任一变量修改该对象,所有引用该对象的变量都会看到这些修改。这种引用行为在处理嵌套数据结构时尤其需要注意,否则可能导致意想不到的结果。

问题重现:嵌套字典的引用陷阱

考虑一个常见的场景:我们有一个初始字典,其值是另一个字典,我们希望遍历这个初始字典,并根据外部数据源(例如Excel文件)动态填充内部字典的值,最终构建一个新的嵌套字典。

假设我们有一个初始字典 initial_dict,结构如下:

initial_dict = {    'LG_G7_Blue_64GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'},    'Asus_ROG_Phone_Nero_128GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'}}

我们希望从一个模拟的Excel工作表 ws 中读取数据,填充 Name、Code 等字段。以下是原始代码尝试实现此功能:

立即学习“Python免费学习笔记(深入)”;

import openpyxlimport datetime# 模拟 openpyxl 的工作表和数据# 在实际应用中,ws 会是一个已加载的 openpyxl 工作表对象class MockCell:    def __init__(self, value):        self.value = valueclass MockWorksheet:    def __init__(self):        self.data = {            'A2': 'LG G7 Blue 64GB', 'B2': 'LG_G7_Blue_64GB_R07', 'C2': datetime.datetime(2005, 9, 25, 0, 0), 'D2': datetime.datetime(2022, 10, 27, 23, 59, 59),            'A3': 'Asus ROG Phone Nero 128GB', 'B3': 'Asus_ROG_Phone_Nero_128GB_R07', 'C3': datetime.datetime(2005, 9, 25, 0, 0), 'D3': datetime.datetime(2022, 10, 27, 23, 59, 59)        }    def __getitem__(self, key):        return MockCell(self.data.get(key, None))ws = MockWorksheet()# 初始字典结构initial_dict = {    'LG_G7_Blue_64GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'},    'Asus_ROG_Phone_Nero_128GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'}}new_dict = {}newest_dict = {}row = 2for k, v in initial_dict.items():    for i, j in v.items():        # 从模拟的 Excel 工作表读取值        cell_ref = j + str(row)        value_from_excel = ws[cell_ref].value        new_dict[i] = value_from_excel    print(f"处理键 '{k}' 后的 new_dict: {new_dict}")    newest_dict[k] = new_dict # 问题所在:这里存储的是 new_dict 的引用    print(f"当前 newest_dict: {newest_dict}")    print("------")    row += 1print("n最终结果 (原始问题代码):")print(newest_dict)

运行上述代码,你会发现最终 newest_dict 的输出并非预期。具体来说,’LG_G7_Blue_64GB_R07′ 和 ‘Asus_ROG_Phone_Nero_128GB_R07’ 这两个键所对应的内层字典值是相同的,都指向了最后一次迭代时 new_dict 的状态。

预期输出(部分):

{'LG_G7_Blue_64GB_R07': {'Name': 'LG G7 Blue 64GB', 'Code': 'LG_G7_Blue_64GB_R07', ...}, 'Asus_ROG_Phone_Nero_128GB_R07': {'Name': 'Asus ROG Phone Nero 128GB', 'Code': 'Asus_ROG_Phone_Nero_128GB_R07', ...}}

实际输出(部分):

一键抠图 一键抠图

在线一键抠图换背景

一键抠图 30 查看详情 一键抠图

{'LG_G7_Blue_64GB_R07': {'Name': 'Asus ROG Phone Nero 128GB', 'Code': 'Asus_ROG_Phone_Nero_128GB_R07', ...}, 'Asus_ROG_Phone_Nero_128GB_R07': {'Name': 'Asus ROG Phone Nero 128GB', 'Code': 'Asus_ROG_Phone_Nero_128GB_R07', ...}}

问题分析:问题的根源在于 new_dict = {} 在外层循环外部只被创建了一次。在每次外层循环迭代中,new_dict 的内容会被更新,但 newest_dict[k] = new_dict 语句仅仅是将 new_dict 这个字典对象的引用存储到了 newest_dict 中。因此,当 new_dict 在后续迭代中被修改时,所有指向它的引用(即 newest_dict 中的所有内层字典)都会反映这些修改,最终它们都指向了 new_dict 最后一次迭代后的状态。

解决方案一:使用 dict.copy() 进行浅拷贝

解决此问题的一种有效方法是在将 new_dict 赋值给 newest_dict 之前,创建一个 new_dict 的副本。Python 字典提供了 copy() 方法,用于执行浅拷贝。

import openpyxlimport datetime# 模拟 openpyxl 的工作表和数据 (同上)class MockCell:    def __init__(self, value):        self.value = valueclass MockWorksheet:    def __init__(self):        self.data = {            'A2': 'LG G7 Blue 64GB', 'B2': 'LG_G7_Blue_64GB_R07', 'C2': datetime.datetime(2005, 9, 25, 0, 0), 'D2': datetime.datetime(2022, 10, 27, 23, 59, 59),            'A3': 'Asus ROG Phone Nero 128GB', 'B3': 'Asus_ROG_Phone_Nero_128GB_R07', 'C3': datetime.datetime(2005, 9, 25, 0, 0), 'D3': datetime.datetime(2022, 10, 27, 23, 59, 59)        }    def __getitem__(self, key):        return MockCell(self.data.get(key, None))ws = MockWorksheet()initial_dict = {    'LG_G7_Blue_64GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'},    'Asus_ROG_Phone_Nero_128GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'}}new_dict = {}newest_dict = {}row = 2print("n--- 解决方案一 (.copy()) 运行 ---")for k, v in initial_dict.items():    # new_dict 在循环外定义,每次迭代填充    # 但是在赋值给 newest_dict 时进行拷贝    for i, j in v.items():        cell_ref = j + str(row)        value_from_excel = ws[cell_ref].value        new_dict[i] = value_from_excel    print(f"处理键 '{k}' 后的 new_dict: {new_dict}")    newest_dict[k] = new_dict.copy() # 关键改动:使用 .copy()    print(f"当前 newest_dict: {newest_dict}")    print("------")    row += 1print("n最终结果 (解决方案一):")print(newest_dict)

通过将 newest_dict[k] = new_dict 改为 newest_dict[k] = new_dict.copy(),我们确保了每次迭代时,newest_dict 存储的是 new_dict 的一个独立副本,而不是其引用。这样,即使 new_dict 在后续迭代中被修改,之前存储的副本也不会受到影响。

注意事项: dict.copy() 执行的是浅拷贝。如果 new_dict 内部的值也是可变对象(例如嵌套列表或字典),那么这些嵌套的可变对象仍然是引用。在当前场景下,new_dict 的值是来自Excel的原始数据(字符串、日期时间对象等),它们通常是不可变或独立的对象,因此浅拷贝已足够。如果内部还有更深层的可变结构需要独立,则可能需要 copy.deepcopy()。

解决方案二:在循环内部重新初始化字典

另一种更简洁且通常更推荐的方法是,在每次外层循环迭代开始时,重新初始化 new_dict。这样可以确保每次迭代都从一个全新的空字典开始构建,从而自然地避免了引用问题。

import openpyxlimport datetime# 模拟 openpyxl 的工作表和数据 (同上)class MockCell:    def __init__(self, value):        self.value = valueclass MockWorksheet:    def __init__(self):        self.data = {            'A2': 'LG G7 Blue 64GB', 'B2': 'LG_G7_Blue_64GB_R07', 'C2': datetime.datetime(2005, 9, 25, 0, 0), 'D2': datetime.datetime(2022, 10, 27, 23, 59, 59),            'A3': 'Asus ROG Phone Nero 128GB', 'B3': 'Asus_ROG_Phone_Nero_128GB_R07', 'C3': datetime.datetime(2005, 9, 25, 0, 0), 'D3': datetime.datetime(2022, 10, 27, 23, 59, 59)        }    def __getitem__(self, key):        return MockCell(self.data.get(key, None))ws = MockWorksheet()initial_dict = {    'LG_G7_Blue_64GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'},    'Asus_ROG_Phone_Nero_128GB_R07': {'Name': 'A', 'Code': 'B', 'Sale Effective Date': 'C', 'Sale Expiration Date': 'D'}}newest_dict = {}row = 2print("n--- 解决方案二 (内部重新初始化) 运行 ---")for k, v in initial_dict.items():    new_dict = {} # 关键改动:每次迭代都创建一个新的 new_dict    for i, j in v.items():        cell_ref = j + str(row)        value_from_excel = ws[cell_ref].value        new_dict[i] = value_from_excel    print(f"处理键 '{k}' 后的 new_dict: {new_dict}")    newest_dict[k] = new_dict # 此时 new_dict 已经是新的对象,可以直接赋值    print(f"当前 newest_dict: {newest_dict}")    print("------")    row += 1print("n最终结果 (解决方案二):")print(newest_dict)

将 new_dict = {} 移动到外层 for 循环内部,意味着在每次处理一个新的 initial_dict 键时,都会创建一个全新的 new_dict 对象。这样,newest_dict[k] = new_dict 语句就会存储对这个新创建的、独立的字典的引用,从而避免了引用冲突。这种方法通常被认为是更清晰、更符合逻辑的解决方案,因为它明确表达了“为每个外层键构建一个独立的内层字典”的意图。

总结与注意事项

在Python中处理可变数据结构(如字典和列表)的嵌套时,理解其引用行为至关重要。当将一个可变对象赋值给另一个变量或将其作为值存储在数据结构中时,通常是传递了对该对象的引用,而不是创建了一个独立的副本。

引用陷阱: 当在循环中重复使用同一个可变对象实例(如 new_dict)并将其赋值给另一个数据结构(如 newest_dict 的值)时,所有这些赋值最终都将指向同一个可变对象。当该对象在后续迭代中被修改时,所有引用都会看到这些修改。解决方案一 (.copy()): 使用 dict.copy() 方法可以创建一个字典

以上就是Python嵌套字典的引用陷阱与解决方案:避免所有键指向同一值的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/582427.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月10日 11:30:09
下一篇 2025年11月10日 11:31:13

相关推荐

  • 为什么选SublimeJ写JS_实时预览省切换窗口

    选择Sublime Text配合LiveReload插件与浏览器扩展,可实现JavaScript代码保存后自动刷新预览;需安装Package Control并添加LiveReload插件,再在Chrome/Firefox中安装对应扩展并启用;为避免跨域问题,应使用Python内置服务器(python…

    2025年12月13日
    000
  • 在哪找SublimeJ Python插件_Package Control安装指南

    首先确认安装Package Control,通过控制台执行安装脚本;成功后使用Ctrl+Shift+P调出命令面板,输入Install Package并搜索SublimeJ Python进行安装;若插件未被索引,可手动添加其GitHub仓库地址后再安装。 如果您在使用 Sublime Text 编辑…

    2025年12月13日
    000
  • 怎么去掉php源码中的注释语句_删php源码注释语句技巧

    1、使用PHP Tokenizer扩展解析标记并过滤T_COMMENT和T_DOC_COMMENT类型注释;2、通过正则///.|/*[sS]?*//g匹配删除单行与多行注释,注意避免误删字符串内容;3、借助PHP-Parser等工具启用strip comments功能批量处理;4、利用OPcach…

    2025年12月13日
    000
  • Laravel Excel导入时生成自定义递增ID的策略与实践

    本文旨在解决在laravel应用中,使用maatwebsite excel导入数据时生成自定义递增id的挑战。针对直接计数或php层生成id可能导致的并发冲突和数据完整性问题,文章提出了一种基于数据库自增id和laravel模型事件的健壮策略。通过详细的代码示例,演示如何在数据模型保存后,利用数据库…

    2025年12月13日
    000
  • 纯HTML实现邮件发送功能:基于mailto协议的表单应用指南

    本文详细介绍了如何利用纯html中的`mailto:`协议实现表单提交后自动触发邮件发送功能。我们将探讨其基本用法、必要的表单属性配置,并提供示例代码。同时,文章还将指出这种客户端邮件发送方式的特点与局限性,帮助开发者理解其适用场景及进阶需求。 在现代Web开发中,实现表单提交后发送电子邮件是常见的…

    2025年12月13日
    000
  • 优化pdftotext输出:消除Form Feed控制字符的教程

    使用`pdftotext`从pdf文件生成文本时,有时会遇到非文本元素(如图像或页面分隔符)被转换成特殊的控制字符(如`ff`、`%0c`或`^l`)。这些字符实际上是form feed(换页符),旨在指示页面边界。本教程将详细介绍如何通过在`pdftotext`命令中添加`-nopgbrk`参数,…

    2025年12月13日
    000
  • 为什么SublimeJ格式化准_按语言智能适配规则

    首先确认文件语言类型是否正确绑定,再检查SublimeJ配置中语言映射与规则文件是否存在,最后通过重装插件修复可能的安装损坏问题。 如果您在使用Sublime Text编辑代码时发现格式化操作未能按预期针对特定语言应用规则,可能是由于插件未正确识别当前文件的语言类型或缺少相应的配置。以下是解决此问题…

    2025年12月13日
    000
  • 怎么用SublimeJ查Bug_正则定位错误日志教程

    使用Sublime Text结合正则表达式可高效定位日志中的错误:1. 打开日志文件并加载内容;2. 启用Ctrl + F搜索及正则模式(点击“.*”图标);3. 输入ERROR|Exception|Traceback等正则筛选关键错误;4. 用^[.?(ERROR|WARN).?]匹配错误级别行;…

    2025年12月13日
    000
  • 怎么调SublimeJ缩进_Python空格4位精准设置

    首先设置空格缩进并指定4空格,再通过语法专属配置仅对Python文件应用该规则,最后手动转换已有代码的缩进以符合规范。 如果您在使用 Sublime Text 编辑 Python 文件时发现缩进不符合规范,尤其是需要统一为 4 个空格而非制表符或其它数量的空格,可以通过以下步骤进行精准设置。 一、修…

    2025年12月13日
    000
  • 如何用SublimeJ写Python_自动补全+语法检查教程

    配置Sublime Text的Python开发环境需安装SublimeJ插件,设置Python解释器路径,启用实时语法检查,自定义补全触发规则,并集成pylint等外部Linter工具以提升编码效率与准确性。 如果您在使用 Sublime Text 编写 Python 代码时希望获得更高效的开发体验…

    2025年12月13日
    000
  • 如何用SublimeJ做代码格式化_Prettier+Black集成

    通过集成Prettier和Black,Sublime Text可实现%ignore_a_1%与Python代码的自动格式化。首先利用Package Control安装JsPrettier和SublimePythonIDE插件;接着通过npm和pip全局或局部安装Prettier与Black工具;然后…

    2025年12月13日
    000
  • 为什么SublimeJ写Python快_轻量无冗余加载解析

    Sublime Text因按需加载模块、无重型后台服务、原生GUI架构及异步插件执行,启动快、响应迅速,编辑Python代码更流畅。 如果您在编写Python代码时希望获得更快的响应速度和更流畅的编辑体验,可能会发现Sublime Text相比其他IDE显得尤为迅速。这主要归因于其架构设计避免了不必…

    2025年12月13日
    000
  • 解决Python向PHP返回多JSON字符串的正确姿势

    本教程旨在解决Python脚本向PHP应用传输多个JSON对象时遇到的常见问题。文章详细介绍了如何优化Python脚本,将所有JSON数据聚合为一个完整的JSON数组并输出。随后,它演示了PHP端如何正确解析这一嵌套结构,通过两次`json_decode`操作,先将整体JSON字符串解码为PHP数组…

    2025年12月13日
    000
  • 如何使用正则表达式精确验证产品代码格式

    本文详细介绍了如何构建一个精确的正则表达式,用于验证特定格式的产品代码,即前两位为大写字母,后四位为数字。文章分析了常见的正则编写错误,例如不当使用量词和字符转义,并提供了正确的表达式及其变体,包括[0-9]和d的互换,以及在不同编程语言(如PHP)中使用时的注意事项,旨在帮助读者掌握正则表达式的正…

    2025年12月13日
    000
  • 将SQL数据转换为JSON并集成到Bootstrap Table的教程

    本教程详细介绍了如何将SQL数据库中的数据通过PHP后端接口转换为Bootstrap Table所需的JSON格式,并实现服务器端分页、排序和搜索功能。通过创建独立的PHP文件作为数据API,配置HTTP响应头,执行PDO查询,并根据Bootstrap Table的特定数据结构(total、rows…

    2025年12月13日
    000
  • php lavarel框架导出文件

    Laravel通过第三方库实现文件导出,常用Laravel Excel导出Excel和CSV,结合DomPDF生成PDF,需注意性能与安全控制。 在 Laravel 框架中导出文件(如 Excel、CSV 或 PDF)是常见需求,比如导出用户数据、订单记录等。Laravel 本身不内置导出功能,但可…

    2025年12月13日
    000
  • php命令行中进行断点

    使用var_dump()和exit()可模拟断点,快速查看变量并暂停执行;通过Xdebug配合IDE实现真正的断点调试,需配置php.ini并启动远程调试模式;PsySH提供交互式调试环境,适合深入排查。注意CLI与Web环境php.ini可能不同,需确保扩展正确启用。 在PHP命令行中进行断点调试…

    2025年12月13日
    000
  • 使用HTML表单实现客户端邮件发送:mailto:方法详解

    本文详细探讨了仅使用HTML表单通过mailto:协议实现客户端邮件发送的方法。我们将介绍其基本语法、如何构建包含主题和内容的表单,并深入分析这种方法的优点、局限性以及在实际应用中需要注意的安全和用户体验问题。同时,也会简要提及更专业的服务器端邮件发送方案,以帮助开发者根据需求选择最合适的策略。 1…

    2025年12月13日
    000
  • 在Laravel Excel导入中实现基于前缀的自定义递增ID策略

    本文探讨了在laravel excel导入过程中生成自定义递增id的健壮方法。针对直接计数行或纯php生成id可能导致的并发和数据完整性问题,文章推荐利用数据库的自增主键,并在记录保存后通过模型层逻辑(如重写`save()`方法或使用模型事件)构造并更新带有特定前缀的自定义递增id,从而确保id的唯…

    2025年12月13日
    000
  • 使用HTML mailto 协议实现表单邮件发送

    本文详细介绍了如何利用纯HTML的`mailto`协议,通过表单提交实现邮件发送功能。重点阐述了`action=”mailto:…”`属性的用法,包括主题和正文的预设,并提供了示例代码。同时,文章深入分析了该方法的客户端特性、局限性(如需要用户确认、依赖邮件客户端…

    2025年12月13日
    000

发表回复

登录后才能评论
关注微信