大家好,我是 SiKi老师。写游戏设置工具时,常见做法是从一份默认配置生成玩家配置,再修改按键。如果修改后的默认值也变了,先检查两份字典是否共享了内层列表,不要急着怀疑保存文件的代码。
本文在 Windows、Python 3.12.14 中运行自拟的内存数据,不读写真实游戏配置。依据是 Python 3.12 copy 文档。示例只讨论普通字典、列表和整数,不测试 Unity、Godot 的资源复制,也不涉及引擎对象生命周期。
一 字典变成两份,列表可能仍是一份
defaults={"volume":50,"keys":["W","S"]}player=defaults.copy()player["volume"]=10player["keys"].append("Space")print(defaults)实际输出{'volume': 50, 'keys': ['W', 'S', 'Space']}。顶层volume仍是 50,但默认按键列表多了Space。为什么两个操作的影响不同?dict.copy()做的是浅拷贝:新建外层字典,内层成员仍引用原来的对象。
给player["volume"]赋值,是替换玩家字典里的这个成员;向player["keys"]追加元素,是修改那份共享列表。不能只看“外层已经 copy 了”,就推断整个数据树都相互独立。
这个小例子也说明,故障不需要等到保存文件才发生。在内存里打印默认值,已经能看见变化。先缩小到复制和修改这两步,能避免把问题混进文件路径、编码和加载流程。
二 检查身份,不只比较内容
在前面的代码后面加两行:
print(playerisdefaults)print(player["keys"]isdefaults["keys"])实测分别是False、True:字典不是同一个对象,按键列表却是同一个对象。这里用is检查对象身份,而不是用==判断内容是否相等。
两份不同的列表可以内容相等;同一份列表当然也可以暂时没有可见差异。因此,只打印两边内容一样,并不能证明复制失败或成功。判断共享关系时,需要把“值是否相同”和“对象是否相同”分开。
如果写的是player = defaults,则连外层字典也没有复制。赋值只是让另一个名字指向同一个对象。这个情况与浅拷贝不同,排查时不要都统称为“copy 没生效”。
三 对普通嵌套数据尝试深拷贝
重新创建默认数据,再运行下面的完整例子;不要沿用前面已经被追加过的defaults。
fromcopyimportdeepcopy defaults={"volume":50,"keys":["W","S"]}player=deepcopy(defaults)player["volume"]=10player["keys"].append("Space")print(defaults)print(player)print(player["keys"]isdefaults["keys"])我得到的输出是:
{'volume': 50, 'keys': ['W', 'S']} {'volume': 10, 'keys': ['W', 'S', 'Space']} False这组普通嵌套数据里,玩家按键列表被修改后,默认列表保持原状。验证重点不是函数名字听起来更完整,而是修改副本后,原始数据是否仍符合预期,以及需要分离的列表是否真的分离。
deepcopy()不是让每个对象都无条件变成全新的对象。它有已复制对象的记录机制,自定义类型也可以规定复制行为;某些类型不能按普通配置数据的方式复制。本文的验证范围不包含文件、套接字、引擎资源或自定义对象。
四 先规定哪些数据应该共享
| 场景 | 需要确认的约定 |
|---|---|
| 玩家修改自己的按键 | 默认按键不应随之改变 |
| 全局只读说明表 | 是否允许各配置共享,如何避免写入 |
| 大型资源或运行时对象 | 是否具有专门的创建、引用或复制机制 |
并不是所有共享引用都是错误。只读数据可能刻意共享;把大型结构全部复制,可能付出额外成本,也可能破坏原本需要共享的关系。因此,不建议看到嵌套结构就批量替换成深拷贝。
如果只需要复制一个按键列表,也可以针对该成员设计复制方案,但应检查其中是否还含有可变对象。复制一层列表并不自动解决更深层的共享。方案应依据实际结构,而不是靠“多加一次 copy”碰运气。
五 用修改副本的测试守住默认值
我会给配置工具加一个小测试:新建默认数据,生成玩家副本,修改玩家按键,然后断言默认列表仍然只有W、S。以后添加按键分组等嵌套结构时,再调整测试覆盖这些新成员。
仅断言复制后的内容相等不够,因为还没进行任何修改;仅断言外层身份不同也不够,因为内部仍可能共享。测试应围绕业务上“不该一起变”的字段设计。
本文只确认了小型内存示例的行为,没有测性能、线程并发或存档迁移,不覆盖真正的游戏输入系统。你需要的是独立的玩家设置,还是几份共同读取的只读模板?先把这个约定说清,再选择复制策略。