0

0

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

心靈之曲

心靈之曲

发布时间:2025-10-26 12:56:01

|

801人浏览过

|

来源于php中文网

原创

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 openpyxl
import datetime

# 模拟 openpyxl 的工作表和数据
# 在实际应用中,ws 会是一个已加载的 openpyxl 工作表对象
class MockCell:
    def __init__(self, value):
        self.value = value

class 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 = 2

for 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 += 1

print("\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', ...}}

实际输出(部分):

微信 WeLM
微信 WeLM

WeLM不是一个直接的对话机器人,而是一个补全用户输入信息的生成模型。

下载
{'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 openpyxl
import datetime

# 模拟 openpyxl 的工作表和数据 (同上)
class MockCell:
    def __init__(self, value):
        self.value = value

class 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 = 2

print("\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 += 1

print("\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 openpyxl
import datetime

# 模拟 openpyxl 的工作表和数据 (同上)
class MockCell:
    def __init__(self, value):
        self.value = value

class 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 = 2

print("\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 += 1

print("\n最终结果 (解决方案二):")
print(newest_dict)

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

总结与注意事项

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

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

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

778

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

686

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

769

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

740

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1445

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

571

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

581

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

752

2023.08.11

拼多多赚钱的5种方法 拼多多赚钱的5种方法
拼多多赚钱的5种方法 拼多多赚钱的5种方法

在拼多多上赚钱主要可以通过无货源模式一件代发、精细化运营特色店铺、参与官方高流量活动、利用拼团机制社交裂变,以及成为多多进宝推广员这5种方法实现。核心策略在于通过低成本、高效率的供应链管理与营销,利用平台社交电商红利实现盈利。

31

2026.01.26

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Excel 教程
Excel 教程

共162课时 | 13.5万人学习

成为PHP架构师-自制PHP框架
成为PHP架构师-自制PHP框架

共28课时 | 2.4万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号