Python copy.replace 怎么更新不可变对象字段
不可变对象的字段不能原地赋值,但业务上经常需要“只改一个字段,其余保持不变”。Python 3.13 新增的 copy.replace(obj, **changes) 就是为这类值对象准备的:它返回一个与原对象同类型的新对象,把 changes 中指定的字段替换掉,原对象保持不变。
官方文档:https://docs.python.org/3/library/copy.html
copy.replace适用于namedtuple、数据类以及实现了__replace__的自定义类。- 它在 Python 3.13 加入,不是
copy.copy或copy.deepcopy的通用替代品。 - 替换发生在字段层面;未替换的嵌套可变对象通常仍会共享引用。
一、把小项目的更新规则定清楚
下面做一个很小的“任务配置更新器”。任务配置一旦创建就不允许原地修改;调整重试次数或负责人时,程序必须生成新配置。验收条件只有四个:返回类型不变、原对象不变、指定字段变更、未指定字段保持原值。
先确认运行环境是 Python 3.13 或更高版本,因为更早版本的 copy 模块没有 replace。如果项目仍需兼容旧版本,数据类可以继续调用 dataclasses.replace,命名元组可以调用自己的 _replace;升级后再把调用方收敛到统一入口。
import sys # copy.replace 从 Python 3.13 开始提供,启动时先明确版本边界。 if sys.version_info
二、用 frozen dataclass 完成一次字段更新
把任务配置定义成 frozen=True 的数据类,可以阻止普通字段赋值。调用 copy.replace 时,不需要把所有字段重新传一遍,只写要变更的字段即可。
from copy import replace
from dataclasses import dataclass
@dataclass(frozen=True)
class TaskConfig:
name: str
retries: int
owner: str
original = TaskConfig(name="daily-report", retries=2, owner="ops")
# 只替换 retries;name 和 owner 从原对象沿用。
updated = replace(original, retries=4)
assert original.retries == 2 # 原对象没有被修改。
assert updated.retries == 4 # 新对象得到目标值。
assert type(updated) is TaskConfig # 返回值保持原类型。
对数据类而言,替换过程会按数据类的构造规则创建新实例,因此 __post_init__ 中的校验仍然重要。不存在的字段会导致异常;标记为 init=False 的字段也不能直接放进 changes,应由初始化逻辑重新计算。

三、把同一写法扩展到 NamedTuple
typing.NamedTuple 创建的是带字段名的元组类型,实例同样不能原地修改。它与 collections.namedtuple 的字段替换能力兼容,因此调用方仍可使用 copy.replace。
from copy import replace
from typing import NamedTuple
class JobKey(NamedTuple):
queue: str
priority: int
key = JobKey(queue="email", priority=3)
# 调用方式与数据类相同,调用方无需改用 _replace。
urgent_key = replace(key, priority=9)
assert key == JobKey(queue="email", priority=3)
assert urgent_key == JobKey(queue="email", priority=9)
统一入口的价值在于上层代码不用先判断“这是数据类还是命名元组”。不过统一不等于万能:普通字典、列表和任意对象并不会因为有字段就自动获得支持,它们应继续使用各自的更新方式。
四、让自定义不可变类接入 __replace__
如果不可变对象既不是数据类也不是命名元组,可以实现 __replace__(self, /, **changes)。这个方法负责校验字段并返回同类型新对象,copy.replace 会把替换请求交给它。
from copy import replace
class Endpoint:
__slots__ = ("_host", "_port")
def __init__(self, host: str, port: int) -> None:
if not 1 str:
return self._host
@property
def port(self) -> int:
return self._port
def __replace__(self, /, **changes):
allowed = {"host", "port"}
unknown = changes.keys() - allowed
if unknown:
raise TypeError(f"不支持的字段: {sorted(unknown)}")
# 未提供的字段沿用当前值,再交给构造器执行统一校验。
return type(self)(
host=changes.get("host", self.host),
port=changes.get("port", self.port),
)
endpoint = Endpoint("api.internal", 443)
backup = replace(endpoint, host="backup.internal")
assert endpoint.host == "api.internal"
assert backup.host == "backup.internal"
assert backup.port == 443
自定义协议最容易出错的地方,是静默忽略未知字段。更稳妥的做法是显式维护允许字段集合,并复用构造器校验,这样直接创建和替换创建遵守同一套约束。
五、验收新对象并避开浅层共享误区
copy.replace 的目标是替换指定字段,不会递归复制所有嵌套值。下面的数据类本身是冻结的,但其中的标签列表仍是可变对象;只替换负责人时,新旧配置会继续引用同一个列表。
from copy import replace
from dataclasses import dataclass
@dataclass(frozen=True)
class DeployPlan:
owner: str
tags: list[str]
plan = DeployPlan(owner="alice", tags=["stable"])
next_plan = replace(plan, owner="bob")
# 未替换的可变字段仍可能共享引用,这不是深拷贝。
assert plan.tags is next_plan.tags
# 若希望标签独立,必须显式提供一个新列表。
independent = replace(plan, owner="bob", tags=[*plan.tags])
assert independent.tags is not plan.tags

项目验收时建议固定检查四项:type(new) is type(old)、原对象字段未变、目标字段已变、嵌套可变字段是否需要独立副本。这样既能享受不可变值对象的清晰语义,也不会把“生成新外层对象”误解成“整棵对象图都已复制”。
相关问题
copy.replace 和 dataclasses.replace 有什么区别?
dataclasses.replace 只处理数据类;copy.replace 提供统一入口,还支持命名元组和实现 __replace__ 的类型。只维护数据类且要兼容 Python 3.12 时,前者仍然合适。
copy.replace 会修改原对象吗?
不会。它应返回同类型的新对象。是否真正保持不可变,还取决于类自身设计以及字段里是否包含列表、字典等可变对象。
为什么普通 class 调用 copy.replace 会失败?
普通类不会自动获得字段替换协议。把它改成数据类,或者自己实现返回同类型对象的 __replace__,才能接入这个接口。
什么时候应该用 deepcopy?
当需求是递归复制完整对象图,而不是只替换少数字段时,才考虑 deepcopy。它的成本和语义都不同,不能仅因为外层对象不可变就默认使用。
Go multipart.SetBoundary 为什么必须在创建 Part 前调用
- 上一篇
- Go multipart.SetBoundary 为什么必须在创建 Part 前调用
- 下一篇
- Lanerc动漫播放卡顿怎么办?编码、解码与缓冲设置说明
-
- 文章 · python教程 | 5小时前 | python ·
- Python NamedTemporaryFile 的 delete_on_close 怎么设置
- 311浏览 收藏
-
- 文章 · python教程 | 11小时前 |
- Python itertools.batched strict 参数什么时候会报错
- 306浏览 收藏
-
- 文章 · python教程 | 13小时前 |
- Python ExceptionGroup split 怎么按异常类型拆分
- 311浏览 收藏
-
- 文章 · python教程 | 14小时前 |
- Python dataclass slots 与 weakref_slot 怎么一起用
- 207浏览 收藏
-
- 文章 · python教程 | 1天前 | python · Python tarfile extraction_filter data_filter
- Python tarfile extraction_filter 怎么阻止危险路径
- 232浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python pathlib.Path.walk 怎么剪枝目录遍历
- 159浏览 收藏
-
- 文章 · python教程 | 1天前 | python · SQLite · Python sqlite3 autocommit isolation_level SQLite事务
- Python sqlite3 autocommit 与 isolation_level 怎么配合
- 378浏览 收藏
-
- 文章 · python教程 | 1天前 | 并发 · 异步编程 · Python教程 · Python 任务调度 asyncio eager_task_factory
- Python asyncio eager task factory 什么时候会改变执行顺序
- 497浏览 收藏
-
- 文章 · python教程 | 1天前 |
- Python zoneinfo部署时区数据缺失的兼容处理
- 378浏览 收藏
-
- 文章 · python教程 | 2天前 |
- Python functools.cache与lru_cache按场景选择的参数清单
- 211浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 248次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 294次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 262次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 246次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 52次使用
-
- go格式“占位符”输入输出 类似python的input
- 2023-01-19 346浏览
-
- Golang如何调用Python代码详解
- 2023-01-07 235浏览
-
- HTTP 的 response 中的响应体和头部是分开发送的吗?
- 2023-01-28 387浏览
-
- B站等视频网站的弹幕用的是 websocket 还是轮询?
- 2023-02-16 447浏览
-
- Linux 下有什么命令行工具以时序显示 CPU 占用率?
- 2023-01-13 360浏览

