python中add()和 __add__() 的用法小结

 更新时间:2026年07月27日 08:57:44   作者:我叫黑大帅  
本文主要介绍了python中add()和 __add__() 的用法小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
@dataclass
class Usage:
    prompt: int = 0
    completion: int = 0
    total: int = 0
    cached: int = 0
    calls: int = 0
    cost: float = 0.0

    def __add__(self, other: "Usage") -> "Usage":
        return Usage(
            prompt=self.prompt + other.prompt,
            completion=self.completion + other.completion,
            total=self.total + other.total,
            cached=self.cached + other.cached,
            calls=self.calls + other.calls,
            cost=self.cost + other.cost,
        )
    
    def add(self, other: "Usage") -> None:
        combined = self + other
        self.prompt = combined.prompt
        self.completion = combined.completion
        self.total = combined.total
        self.cached = combined.cached
        self.calls = combined.calls
        self.cost = combined.cost

先看区别:

方案 1:add()原地修改

def add(self, other: "Usage") -> None:
    self.prompt += other.prompt
    self.completion += other.completion
    self.total += other.total
    self.cached += other.cached
    self.calls += other.calls
    self.cost += other.cost

使用:

total = Usage()
total.add(usage1)
total.add(usage2)

✅ 优点:性能更好

不会创建新对象,适合大量累加:

usage = Usage()

for item in usages:
    usage.add(item)

避免大量临时 Usage() 对象。

❌ 缺点:会改变自身

例如:

a = Usage(prompt=100)
b = a

a.add(Usage(prompt=50))

print(b.prompt)

结果:

150

因为 ab 指向同一个对象。

这可能导致隐藏 bug。

方案 2:__add__()返回新对象

def __add__(self, other: "Usage") -> "Usage":
    return Usage(
        prompt=self.prompt + other.prompt,
        completion=self.completion + other.completion,
        total=self.total + other.total,
        cached=self.cached + other.cached,
        calls=self.calls + other.calls,
        cost=self.cost + other.cost,
    )

使用:

total = usage1 + usage2

✅ 优点:更安全

例如:

total = usage1 + usage2

print(usage1)

usage1 不会改变。

这符合函数式思想:

输入不变,输出新值

支持链式计算

total = u1 + u2 + u3 + u4

❌ 缺点:创建很多对象

result = Usage()

for u in usages:
    result = result + u

实际上:

第1次:
Usage对象A + u1 -> B

第2次:
B + u2 -> C

第3次:
C + u3 -> D

现在我们看原地更改的问题: 可以直接看3.

1. 引用共享导致意外修改(复制引用)

例如:

usage = Usage(prompt=100)

backup = usage

usage.add(Usage(prompt=50))

print(backup.prompt)

结果:

150

很多人会以为:

backup = usage

是复制,但 Python 只是复制引用

图示:

backup ─┐
        ├──> Usage(prompt=100)
usage ──┘

add() 修改的是这个对象本身。

2. 作为函数参数传递时 (更改全局状态)

例如:

def calculate_total(usage):
    usage.add(Usage(prompt=100))
    return usage

u = Usage(prompt=10)

result = calculate_total(u)

print(u.prompt)

输出:

110

3. 多线程环境下可能出现数据竞争

例如你的场景:

  • 多个 LLM 请求同时完成
  • 都更新一个全局 Usage

类似:

global_usage.add(response_usage)

线程 A:

读取 prompt=100
+50
写回150

线程 B:

读取 prompt=100
+30
写回130

最终:

130

而不是:

180

为:

self.prompt += other.prompt

不是原子操作。

实际上等价:

tmp = self.prompt
tmp = tmp + other.prompt
self.prompt = tmp

多线程会丢更新。

解决: 加锁

from threading import Lock

lock = Lock()

with lock:
    usage.add(other)

我的选择:add()保持现有框架

是的,add() 本身不是线程安全的.

不过重点是:当前这份代码里,add() 暂时没有并发问题。

每个线程里,各自跑自己的 Workflow

原因是现在的并发结构是这样的:

  • 每个并发任务都会在 run_source() 里创建自己的 Workflow
  • 每个 Workflow 有自己的 self.usage
  • source_usage 也是每个任务自己的局部变量
  • global_usage 只在主线程的 for future in as_completed(futures) 里合并
# 每个线程:
run_source()
  -> Workflow()
  -> workflow.usage
  -> source_usage

# 主线程:
as_completed(futures)
  -> 拿到某个 source_usage
  -> merge_usage_map(global_usage, source_usage)

现在没有多个线程同时对同一个 Usage 对象执行:

usage.add(other)

所以当前代码的数据统计不会因为 add() 并发写而乱掉。

真正会出问题的是这种写法:(在最后全局统计时)

global_usage = {}

# 多个 worker 线程里同时执行
merge_usage_map(global_usage, workflow.usage)

两个线程同时加同一个模型时,可能出现丢计数。

现在的最后统计:

for future in as_completed(futures):
    title, success, error, usage = future.result()
    merge_usage_map(global_usage, usage)

虽然前面 5 个 worker 是并发跑的,但它们只是各自返回自己的usage,真正合并到 global_usage 的动作,是主线程一个 future 一个 future 地处理。

worker 1 跑完 -> 返回 usage
worker 2 跑完 -> 返回 usage
worker 3 跑完 -> 返回 usage

主线程:
合并 worker 2 的 usage
合并 worker 1 的 usage
合并 worker 3 的 usage

哪个任务先结束,就先汇总哪个;没结束的任务暂时不会汇总。

# 5 个并发任务启动

第 1 本完成 -> add 到 global_usage
第 3 本完成 -> add 到 global_usage
第 5 本还在跑 -> 暂时没 add
第 2 本还在跑 -> 暂时没 add
第 4 本还在跑 -> 暂时没 add

并发执行,串行汇总。

串行汇总? 会不因为前面的某个线程很慢,而卡住后面的线程

as_completed(futures)的特点是:哪个 future 先完成,就先把哪个吐出来。

同时跑:1 2 3 4 5

任务 3 先完成
-> 主线程立刻拿到任务 3 的 usage
-> merge_usage_map(global_usage, 任务3_usage)
-> 线程池空出一个位置,开始任务 6

任务 1 完成
-> 立刻 add 任务 1 的 usage
-> 开始任务 7

任务 5 很慢
-> 它没完成前不会 add 它自己的 usage
-> 但不会阻塞任务 1/2/3/4/6/7/... 的 add

只有一种等待:最后总函数返回前,会等全部 100 个 future 都完成。因为最终全局总合必须包含所有任务。

所以结论是:

当前代码:没有并发问题。
因为全局合并发生在主线程。

add() 本身:不是线程安全的。

最稳的写法是保持现在这个架构:worker 只返回自己的 usage,主线程统一合并。

到此这篇关于python中add()和 __add__() 的用法小结的文章就介绍到这了,更多相关python中add()和 __add__() 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • python使用openCV遍历文件夹里所有视频文件并保存成图片

    python使用openCV遍历文件夹里所有视频文件并保存成图片

    这篇文章主要介绍了python使用openCV遍历文件夹里所有视频文件并保存成图片,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友参考下吧
    2020-01-01
  • Python读取系统文件夹内所有文件并统计数量的方法

    Python读取系统文件夹内所有文件并统计数量的方法

    今天小编就为大家分享一篇Python读取系统文件夹内所有文件并统计数量的方法,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2018-10-10
  • python中类变量与成员变量的使用注意点总结

    python中类变量与成员变量的使用注意点总结

    python 的类中主要会使用的两种变量:类变量与成员变量。类变量是类所有实例化对象共有的,而成员变量是每个实例化对象自身特有的。下面这篇文章主要给大家介绍了在python中类变量与成员变量的一些使用注意点,需要的朋友可以参考借鉴,下面来一起看看吧。
    2017-04-04
  • python闭包和装饰器你了解吗

    python闭包和装饰器你了解吗

    这篇文章主要为大家详细介绍了Python的闭包和装饰器,何为闭包?何为装饰器?感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2021-08-08
  • Python实现对Excel文件中不在指定区间内的数据加以去除的方法

    Python实现对Excel文件中不在指定区间内的数据加以去除的方法

    这篇文章主要介绍了基于Python语言,读取Excel表格文件,基于我们给定的规则,对其中的数据加以筛选,将不在指定数据范围内的数据剔除,保留符合我们需要的数据的方法,需要的朋友可以参考下
    2023-08-08
  • python保留两位小数的3种方法实例

    python保留两位小数的3种方法实例

    保留小数位是我们经常会碰到的问题,尤其是刷题过程中,下面这篇文章主要给大家介绍了关于python保留两位小数的3种方法,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2022-12-12
  • 使用Python+wxpy 找出微信里把你删除的好友实例

    使用Python+wxpy 找出微信里把你删除的好友实例

    今天小编就为大家分享一篇使用Python+wxpy 找出微信里把你删除的好友实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2019-02-02
  • Python Request类源码实现方法及原理解析

    Python Request类源码实现方法及原理解析

    这篇文章主要介绍了Python Request类源码实现方法及原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Django代码性能优化与Pycharm Profile使用详解

    Django代码性能优化与Pycharm Profile使用详解

    本文通过一个简单的实例一步一步引导读者对其进行全方位的性能优化,这篇文章主要给大家介绍了关于Django代码性能优化与Pycharm Profile使用的相关资料,文中通过示例代码介绍的非常详细,需要的朋友可以参考下
    2018-08-08
  • pycharm专业版远程登录服务器的详细教程

    pycharm专业版远程登录服务器的详细教程

    这篇文章主要介绍了pycharm专业版远程登录服务器的详细教程,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-09-09

最新评论