-
我在 Python 里用内置的 round 给一批金额做四舍五入本以为这是小学就会的常识级操作不可能出错,结果对账时总额老是差那么几分钱单个数也对不上,我把 round(0.5) 打出来看到的竟是 0 而不是 1 当场就懵了,排查很久才搞懂 Python3 的 round 用的根本不是我以为的四舍五入而是四舍六入五成双的银行家舍入的深度复盘
我做一个涉及金额计算的功能、要把一堆金额四舍五入到整数或两位小数,想都没想就用了 Python 内置 round——这不就是四舍五入嘛。可上线后对账出问题:把一批数各自 round 再求和、总额和财务按四舍五入算的预期值总差那么几分钱平不了;挑几个 .5 结尾的单独试更头皮发麻——round(0.5)=0 不是 1、round(2.5)=2 不是 3、但 round(1.5)=2、round(3.…- 0
- 0
-
我在 Python 里用 zip 把两个列表配成一对对、自以为天衣无缝,可下游的数据莫名其妙少了一截、有些记录凭空消失,我反复检查源数据明明都在,最后才发现两个列表长度差了几个而 zip 会在最短的那个用完时就悄悄停下长的那个多出来的全被它一声不吭地丢掉了的深度复盘
我有两批数据要配对:一批 ids、一批 values,本应一一对应,很自然写 for id, value in zip(ids, values) 配成对处理,测试数据一切正常。可上线后下游报数据少了:本应处理 1000 条实际只处理了 997 条、有几条记录像凭空消失既没报错日志也没异常。我反复核对源头那几条数据明明都在,直到打印 len(ids) 和 len(values) 才发现两个列表长度不…- 0
- 0
-
我在 Python 里想用一个列表当字典的 key 来做分组、又想把一批列表丢进集合去重,结果两处都甩给我一个 TypeError unhashable type list,我一开始以为是 Python 小题大做后来才真正想明白可变的东西天生就没法做哈希定位的依据的深度复盘
我有两个需求:一是想按一组坐标或一组标签给数据分组、自然想到用那组值(一个列表)当字典 key 比如 groups[[1,2]]=...;二是有一批列表想去重、自然想到丢进 set。结果一运行两处都报 TypeError: unhashable type: 'list'。我当时有点恼火、觉得不就是拿列表当 key、放列表进集合吗至于报错,以为语法错了(没有)、以为版本限制(各版…- 0
- 0
-
我在 Python 类里直接写了个空列表当属性、想让每个对象都有自己独立的一份,结果给一个对象的列表追加元素、所有对象的列表里竟然都冒出了同一个元素,我对着代码看了半天都不敢相信,最后才搞懂那个列表根本不属于任何一个对象而是被全体实例共用的同一个的深度复盘
我写了个表示用户的类,想给每个用户一个标签列表,图省事直接在类体里写了 tags = [],以为这样每 new 一个对象它就有一个属于自己的空列表。然后给某个用户 append 一个标签,准备只给他打标。可诡异的事发生了:我只给 user_a 加了一个标签,打印 user_b、user_c 的 tags 里面竟然也有那个标签。我以为写错了引用、反复检查 append 的对象确实只动了 user_a…- 0
- 0
-
我把一个嵌套的字典拷贝了一份、想改副本而不动原件,结果一改副本里层的数据、原件竟然也跟着变了,排查半天才发现我用的拷贝只复制了最外层、里面那层还是和原件共享的同一个对象的深度复盘
我有个嵌套的数据结构(字典里的值又是列表/字典),要在不影响原件的前提下拷一份出来改改。我很自然用 copy()(或切片 [:]、dict(原件))拷了一份,心想这下副本和原件就是两份独立数据了,随便改副本原件纹丝不动。可改着改着诡异的事发生了:我明明只改了副本里嵌套那层的数据,一回头原件里对应那层也跟着变了。我盯着代码反复看,怀疑哪里引用串了、别处偷偷改了原件,查半天没头绪。直到深究 copy(…- 0
- 0
-
我用 Python 读写文件一直好好的,可代码一换到别的机器上跑,要么读出来一堆乱码、要么直接抛 UnicodeDecodeError,排查半天才发现我从来没指定过编码、一直在默默依赖一个会随环境变的默认值的深度复盘
我有段处理文本文件的 Python 代码,读文件、处理、写回去,写法朴实:open(path) 读、open(path,'w') 写,从没想过编码。在我开发机上一直稳稳当当,中文各种字符读写都分毫不差。可部署到别的机器、或同事在不同系统上跑就爆发了:有的环境读出来的中文变成锟斤拷似的乱码,有的直接抛 UnicodeDecodeError 崩溃。同一段代码同一个文件,在我这儿好端…- 0
- 0
-
我在 Python 的循环里批量造了一串函数,每个本该记住自己那一轮的编号,结果调用时它们却异口同声地全返回了最后一个数,排查半天发现闭包记住的是变量本身、而不是当时那个值的深度复盘
我有个需求:根据一个列表批量生成一串回调函数,每个被调用时应返回它对应的索引——第 0 个返回 0、第 1 个返回 1。我很自然地在 for 循环里用 lambda 一个个造出来,自觉天衣无缝。可一调用就傻眼:这串函数无论调哪一个,返回的全是同一个数——最后一个索引!它们仿佛集体失忆,谁也没记住自己出生那一刻的编号,而是异口同声报出同一个数。我盯着那段再正常不过的循环百思不得其解:明明每轮 i 都…- 0
- 0
-
我想用多线程加速一段纯计算的代码,开了 8 个线程满心以为能快 8 倍,结果不但没快、反而比单线程还慢,因为 Python 有个 GIL、同一时刻只让一个线程真正在算的深度复盘
我有一段 CPU 密集的计算(大量纯数值运算),单线程跑得慢,我想这台机器有 8 核、开 8 个线程并行算不就快 8 倍?于是用 threading 开了 8 个线程分摊计算。结果一测:不但没快 8 倍,反而比单线程还慢,CPU 监控显示始终只有一个核在忙、其他核闲着。复盘才搞懂:CPython 有一个 GIL(全局解释器锁),同一时刻只允许一个线程执行 Python 字节码;我开的 8 个线程没…- 0
- 0
-
我在 Python 里用 is 来判断两个数是否相等,小数值时一直好好的,某次数值大了一点 is 突然返回 False,因为 is 比的根本不是值相等而是是不是同一个对象,只是小整数被缓存了才碰巧一致的深度复盘
我有段代码判断一个数值是否等于某个特定值,写成了 if count is 1000,以为 is 就是判断相等。开发和小数据测试时一直好好的,可上线数据一变就出诡异问题:有时 count 明明就是 1000,count is 1000 却返回 False。盯着 count == 1000 是 True、count is 1000 却是 False 百思不解。复盘才搞懂:== 比较的是两个对象的值是否…- 2
- 0
-
我在 Python 里一边遍历字典一边删掉满足条件的键,本以为天经地义,结果程序直接抛 RuntimeError 说字典在迭代时改变了大小:一次遍历时修改容器、误以为可以边用边改的深度复盘
我有个字典 cache,想把所有过期条目删掉,顺手写了 for key in cache: if is_expired(cache[key]): del cache[key]——遍历每个键、过期就删,多直白。可一运行就崩:RuntimeError: dictionary changed size during iteration(字典在迭代中改变了大小)。删个字典元素怎么还犯法了?复盘迭代器机制才…- 0
- 0
-
我在批处理循环里写了 try except pass,觉得出错就跳过别让程序崩很稳健,结果一批数据悄悄少处理了一半还谁都不知道,连 Ctrl+C 都按不停:一次裸 except 吞掉异常的深度复盘
我有个批处理循环处理一批数据,为了健壮给循环体包了 try: ... except: pass(出错就跳过继续)。可线上跑完对账发现处理结果悄悄少了一大半:本该 1 万条只成功几千条,另外几千条出错被 except: pass 静默吞掉跳过、没有任何日志和报警谁都不知道;更离谱的是跑的时候 Ctrl+C 想停居然按不停。查清才明白用裸 except + pass 的两宗罪:pass 把异常静默吞掉…- 0
- 0
-
我用乘法快速初始化了一个二维列表,给其中一个格子赋值,结果发现每一行的那个格子都跟着变了:一次 Python 列表乘法复制的是引用而非副本的深度复盘
我要初始化一个 3×3 的二维列表,图省事用了乘法 grid = [[0]*3]*3,打印出来 [[0,0,0],[0,0,0],[0,0,0]] 看着完全正确。可当我 grid[0][0]=1 时,grid 变成了 [[1,0,0],[1,0,0],[1,0,0]]——明明只改了第 0 行第 0 列,每一行的第 0 列都变成了 1。打印 id 才看明白:列表的 *3 复制的是引用而不是副本,它把…- 0
- 0
-
我用 float 累加订单金额,对账时发现总额差了几分钱、还有一笔订单因为金额判等永远不成立而卡住:一次 Python 浮点数精度坑、钱绝不能用 float 表示的深度复盘
我们的订单系统金额计算图省事全用了 Python 的 float,结果炸出两个 bug:财务月底对账,系统总金额和逐笔加起来的正确值差了几分钱、死活对不平;还有一笔订单明明金额一分不差,if paid_amount == order_amount 这个判付清的判断却永远是 False、订单一直卡在未付清。在解释器里敲 0.1 + 0.2 才看明白:它不等于 0.3、而是 0.30000000000…- 0
- 0
-
一个生成器我先遍历了一遍算总数、再遍历一遍做处理,结果第二遍啥也没有、处理了零条数据:一次 Python 迭代器只能消费一次、把一次性的流当成可反复遍历的列表的深度复盘
我写脚本批量处理数据,逻辑很直白——先遍历一遍算出总共多少条打印进度,再遍历一遍真正处理每一条。本地测着没问题,上线处理大数据日志却显示共 0 条待处理、一条都没处理。打印那个变量才看清:它不是 list,而是个生成器(迭代器);迭代器只能消费一次,第一遍 sum 算总数时就把它的游标走到了尽头、消费光了,第二遍遍历时它已经空了。这篇复盘从故障现场讲到迭代器为什么只能消费一次、它和列表的本质区别(…- 0
- 0
-
一段用浅拷贝复制配置模板的 Python 代码,我改了副本里的一个嵌套列表,结果把原始模板和其他所有副本一起改了:一次浅拷贝陷阱的深度复盘
基于一个嵌套配置模板生成多份各自独立的配置,用 copy.copy 复制后改一份,结果原模板和其他所有副本里的那个嵌套列表全跟着变了。根因是 copy.copy/dict.copy/list[:] 都是浅拷贝:只复制最外层容器,里面嵌套的 list/dict 复制的只是引用、和原来共享同一个对象,改副本的嵌套对象其实改的是大家共享的那一个。本文讲透浅拷贝与深拷贝、Python 变量是引用的本质,给…- 3
- 0
-
一段用多线程给 CPU 密集计算加速的 Python 代码,开了八个线程却比单线程还慢,我被 GIL 实实在在上了一课:一次多线程并行误区的深度复盘
一个 CPU 密集计算单线程跑十几秒,我想当然地以为 8 核机器开 8 个线程能快好几倍,结果不但没快反而更慢,而且 8 核机器 CPU 利用率始终只有一个核的量。根因是 CPython 的 GIL(全局解释器锁):同一时刻只有一个线程能执行 Python 字节码,8 个线程只能轮流抢 GIL、本质串行用不上多核,还多了切换开销。本文讲透 GIL 是什么、为何让多线程对 CPU 密集无效却对 IO…- 0
- 0
-
一段用 float 累加金额的 Python 代码,在几万笔订单后对账差了几分钱,让我栽进了二进制浮点精度的坑:一次用错数值类型的深度复盘
财务追了三天:Python 日终对账总额和逐笔加起来的应有总额每天差几分钱,飘忽不定,逻辑却怎么看都没错。直到在终端敲下 0.1 + 0.2 得到 0.30000000000000004,才惊觉根因是用 float 累加金额——float 是二进制浮点,0.1 这类十进制小数在二进制里无限循环、只能存近似值,每笔金额带微小误差,几万笔累加飘出几分钱,且 0.1+0.2==0.3 为 False。本…- 0
- 0
-
我先用生成器算了个总数,再想遍历它处理数据,结果第二次遍历竟然一个元素都没有,我对着 Python 生成器只能迭代一次、用完就耗尽这个坑排查了大半天的复盘
一个让我对 Python 生成器到底是什么彻底搞明白的坑,诡异在我拿着同一个变量第一次遍历它好好的有一堆数据、第二次遍历它却空空如也一个元素都没有,就好像数据被偷偷搬空了。要先统计一批数据总数再逐个处理,用了生成器既省内存又优雅:data = (x for x in source if x.is_valid);count = sum(1 for _ in data) 算出 100 条;然后 for…- 2
- 0
-
我在循环里批量创建了一组函数,本想让它们各自记住循环时的值,结果调用时它们全都返回了循环结束后那个最终值,我对着 Python 闭包捕获的是变量而非值这个延迟绑定的坑排查大半天的复盘
一个让我对 Python 闭包到底记住了什么彻底搞明白的坑,诡异在我分明在循环里给每个函数都用了当时的循环值,可这些函数全都失忆了——记住的不是各自创建时的值而是所有函数共享的循环结束后同一个最终值。要在循环里批量创建一组函数,每个应记住对应的循环值:for i in range(3): funcs.append(lambda: i)。期望调用得到 [0,1,2],实际却是 [2,2,2] 全是 …- 2
- 0
-
我写了个函数给参数设了个空列表默认值,第二次调用时却发现里面莫名其妙带着上一次的数据,我对着 Python 可变默认参数在定义时只创建一次被所有调用共享这个坑排查大半天的复盘
一个堪称 Python 头号经典陷阱的坑,几乎每个 Python 程序员都会栽一次,诡异在出问题的函数每次调用代码一模一样、行为却随调用次数改变,仿佛函数有记忆。写了个给篮子添加物品的函数,为了不传篮子时就新建空篮子,很自然给参数设了 [] 默认值:def add_item(item, basket=[]): basket.append(item); return basket。第一次 add_i…- 0
- 0
-
我用 [[0]*m]*n 飞快地建了个二维数组,往里填一个格子的值,结果整整一列都跟着变了,我对着 Python 列表乘法复制的是引用而不是值这个坑排查大半天的复盘
一个看着人畜无害、却让我对着输出怀疑大半天人生的 Python 坑。需求很普通:做一个棋盘/网格小算法,要一个 n 行 m 列、初始全 0 的二维数组。凭着对 Python 简洁的信赖,我顺手写下自以为最 Pythonic 的一行 grid = [[0]*m]*n,打印出来 [[0,0,0,0],...] 完美。然后往左上角填值 grid[0][0]=1,结果打印发现三行的第一个都变成 1 了!我…- 0
- 0
-
我用 Python 多线程并行跑 CPU 密集计算想提速,结果开了 8 个线程比单线程还慢,我对着 GIL 排查了大半天的复盘
写了个 CPU 密集型计算任务,想当然用多线程并行加速,开 8 个线程以为快 8 倍。结果目瞪口呆:开 8 个线程不但没快反而比单线程还慢一点;CPU 监控更困惑——8 核机器,8 个线程加起来只用满约 1 个核,其他 7 个核基本闲着。排查大半天才理解 Python 又爱又恨绕不开的设计——GIL(全局解释器锁):它规定同一时刻只有一个线程能执行 Python 字节码,所以即使开 8 线程、有 …- 0
- 0
-
我用 Python 的 float 累加金额做对账,几万笔加下来总额和数据库就是差了几分钱,我对着浮点数精度排查了大半天的复盘
写了个对账脚本,把几万笔交易金额用 Python float 累加和数据库总额核对,逻辑简单到不能再简单,结果死活对不上——我算的总额和数据库总是差那么几分钱。反复检查数据没漏没重、加法没写错,可几万笔加完就是差 0.03 这种诡异小数。一度怀疑数据库、怀疑数据精度,最后敲了行 0.1 + 0.2 看到输出 0.30000000000000004 才恍然大悟:问题出在 float 本身,它根本存不…- 0
- 0
-
我的 Python 函数返回的数据,第一次遍历好好的、第二次却空空如也,我对着生成器只能消费一次这个坑排查了大半天的复盘
写了个数据处理脚本,一个函数返回一批记录,调用方先遍历一遍统计总数打进度、再遍历一遍写数据库,逻辑顺得不能再顺。结果诡异:日志清清楚楚"共 5000 条",数据库里却一条都没写进去。盯着代码反复看,两次遍历代码一模一样,凭什么第一次数出 5000、第二次一条没有?甚至怀疑数据库连接,查半天没问题。排查大半天才撞上 Python 对新手极隐蔽的坑:生成器只能被消费一次。根因是函数…- 0
- 0
Python
幸运之星正在降临...
点击领取今天的签到奖励!
恭喜!您今天获得了{{mission.data.mission.credit}}积分
我的优惠劵
-
¥优惠劵使用时效:无法使用使用时效:
之前
使用时效:永久有效优惠劵ID:×
没有优惠劵可用!





















![我用 [[0]*m]*n 飞快地建了个二维数组,往里填一个格子的值,结果整整一列都跟着变了,我对着 Python 列表乘法复制的是引用而不是值这个坑排查大半天的复盘](https://blog.biekanle.com/wp-content/uploads/thumb/2026/06/fill_w526_h326_g0_mark_manual_458_cover.webp)


