尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

「Python 翻车日记 · 第 1 篇」函数记住上次的数据了?Python 是不是在针对我!

发布时间:2026/9/6 6:21:18

资讯中心
01
ARTICLE

「Python 翻车日记 · 第 1 篇」函数记住上次的数据了?Python 是不是在针对我!

「Python 翻车日记 · 第 1 篇」函数记住上次的数据了?Python 是不是在针对我!
「Python 翻车日记 · 第 1 篇」函数记住上次的数据了Python 是不是在针对我本期菜单9 个坑 1 个根因从「函数有记忆」到「标签思维」[入门] 默认参数是钉子户 · 列表乘法是复读机 · 循环删元素会瞬移 · 赋值不是复制[进阶] 浅拷贝只换外套 ·会变脸 · 元组并不老实 · 类变量全班共享 ·setdefault偷偷写[深入] 一切根源变量不是盒子是标签开篇 · 深夜求救晚上十一点半手机弹出毛毛姐的微信。她兴冲冲说自己写了个超级厉害的函数能自动记录粉丝昵称——结果运行三次每次之前的数据都没清掉。defadd_fan(name,fan_list[]):fan_list.append(name)returnfan_listprint(add_fan(铁柱))# [铁柱]print(add_fan(翠花))# [铁柱, 翠花] ← 铁柱怎么还在print(add_fan(狗蛋))# [铁柱, 翠花, 狗蛋] ← 越攒越多毛毛姐「为什么我让它记住一个它每次都把之前的也记住了Python 是不是在针对我」我「行今晚给你讲清楚。不过听完你得请我喝奶茶。」坑点 1可变默认参数——毛毛姐的幽灵缓存 [入门]我「Python 函数定义里的[]不是每次调用时创建的而是在你写def那一行就创建好了——像一个钉子户每次调用都改的是同一个列表。」# 证据函数对象的隐藏属性 __defaults__defadd_fan(name,fan_list[]):fan_list.append(name)returnfan_listprint(add_fan.__defaults__)# ([],) ← 还没调用空列表已经在了add_fan(铁柱)print(add_fan.__defaults__)# ([铁柱],)add_fan(翠花)print(add_fan.__defaults__)# ([铁柱, 翠花],)add_fan(狗蛋)print(add_fan.__defaults__)# ([铁柱, 翠花, 狗蛋],) ← 滚雪球毛毛姐「所以不是 Python 针对我是我没跟人家说清楚要新建列表。」我「正确写法用None做哨兵——如果是 None现场 new 一个空列表。」# ✅ None 哨兵模式defadd_fan(name,fan_listNone):iffan_listisNone:fan_list[]fan_list.append(name)returnfan_listprint(add_fan(铁柱))# [铁柱]print(add_fan(翠花))# [翠花] ← 干净了print(add_fan(狗蛋))# [狗蛋]为什么None就可以关键不在None「不可变」而在哨兵机制本身每次调用时if fan_list is None检查到None就现场新建一个列表从不复用同一个默认对象所以各次调用彼此独立。因此更准确的规则是——默认值不要用会被修改的可变对象列表、字典、集合要注意元组虽然标着「不可变」但装进可变元素如([],)后照样不安全坑点 7 会细讲。一句话带走默认值在def时就创建了用None做哨兵别用[] {} set()坑点 2列表乘法——改一个格子整张表都乱了 [入门]毛毛姐想建一个 3×3 表格存粉丝等级结果改一个格子整列都变了。# ❌ 列表乘法是引用复制table[[]]*3table[0].append(铁柱)print(table)# [[铁柱], [铁柱], [铁柱]] ← 全改了我「*n是复制引用不是复制数据。就像复印了三张写了同一个地址的名片——指向同一个房子。」# ✅ 列表推导式每个子列表独立创建table[[]for_inrange(3)]table[0].append(铁柱)print(table)# [[铁柱], [], []] ✓一句话带走*n复制的是引用不是数据。矩阵用列表推导式坑点 3循环里删元素——删完发现漏了好几个 [入门]毛毛姐想过滤掉僵尸粉结果删完还漏了一个——狗蛋居然活下来了。# ❌ 边遍历边删除fans[铁柱,狗蛋,翠花,建国,春花]forfaninfans:iffanin[铁柱,狗蛋,建国]:fans.remove(fan)print(fans)# [狗蛋, 翠花, 春花] ← 狗蛋怎么还在我「for fan in fans边取边删。删掉铁柱索引 0后后面的元素整体前移狗蛋顶到了索引 0可循环指针已经走到索引 1翠花——狗蛋就这样被跳过了成了漏网之鱼。」# ✅ 方案一列表推导式最推荐fans[fanforfaninfansiffannotin[铁柱,狗蛋,建国]]# ✅ 方案二倒序遍历从后往前索引不会乱foriinrange(len(fans)-1,-1,-1):iffans[i]in[铁柱,狗蛋,建国]:delfans[i]一句话带走边遍历边删除索引会乱用列表推导式或倒序遍历坑点 4赋值不是复制——难兄难弟 [入门]毛毛姐以为backup original是备份结果改副本原件也变了。# ❌ 这不是复制是贴第二张标签original[铁柱,翠花,狗蛋]backuporiginal backup.append(春花)print(original)# [铁柱, 翠花, 狗蛋, 春花] ← 原件也被改了我「Python 里变量不是盒子是标签。backup original是把backup这张标签也贴到同一个列表对象上。」# ✅ 真正复制的方法backuporiginal.copy()# 方法1backuporiginal[:]# 方法2backuplist(original)# 方法3一句话带走赋值是贴标签不是复制。真要复制用.copy()、[:]、list()坑点 5浅拷贝 vs 深拷贝——嵌套列表的隐形炸弹 [进阶]两个小时后毛毛姐炸了——她用了.copy()嵌套列表的【内层还是共享的】。# ❌ copy() 只复制外层内层还是同一个对象fans_data[[铁柱,5],[翠花,3]]backupfans_data.copy()backup[0][1]99print(fans_data)# [[铁柱, 99], [翠花, 3]] ← 原件也被改了我「浅拷贝就像换了一件外套内衬没换。外层是新的内层还是原来的对象。」# ✅ 嵌套结构必须用深拷贝importcopy fans_data[[铁柱,5],[翠花,3]]backupcopy.deepcopy(fans_data)# 连内层对象也递归复制backup[0][1]99print(fans_data)# [[铁柱, 5], [翠花, 3]] ← 原件不受影响 ✓一句话带走一层用copy()嵌套用deepcopy()坑点 6的变脸表演 [进阶]毛毛姐发现同一个在列表和元组上结果不一样。# 对列表就地修改a[1,2]ba a[3]print(a)# [1, 2, 3]print(b)# [1, 2, 3] ← b 跟着变了# 对元组创建新对象a(1,2)ba a(3,)print(a)# (1, 2, 3)print(b)# (1, 2) ← b 没变我「先尝试调用__iadd__方法——“请在自己身上完成加法”。列表有这个方法就地修改元组没有退化为a a (3,)生成新元组贴标签——原来的 b 还指着旧元组。」一句话带走对列表就地改对元组建新的坑点 7元组的不可变其实是假象 [进阶]毛毛姐发现了元组的秘密——说好的不可变呢t([1,2],[3,4])t[0].append(99)print(t)# ([1, 2, 99], [3, 4]) ← 明明改了啊我「元组的’不可变’指的是元组里存的那些引用不能变——t[0]永远指向那个列表对象。但列表里面的内容元组管不着。」毛毛姐「就像租房子——合同不能换但房间里怎么装修你管不着。」一句话带走元组锁的是引用不是锁引用指向的对象内容坑点 8类变量——所有学生共用一个成绩单 [进阶]三天后毛毛姐开始学面向对象——铁柱的成绩跑到了翠花成绩单里。# ❌ 这是类变量——整个类共享一份classStudent:scores[]defadd_score(self,score):self.scores.append(score)s1Student()s2Student()s1.add_score(90)s2.add_score(85)print(s1.scores)# [90, 85] ← 成绩串了print(s2.scores)# [90, 85]# ✅ 在 __init__ 中初始化——每个实例独立classStudent:def__init__(self):self.scores[]defadd_score(self,score):self.scores.append(score)s1Student()s2Student()s1.add_score(90)s2.add_score(85)print(s1.scores)# [90] ✓print(s2.scores)# [85] ✓位置类型共享class下面、__init__外面类变量是__init__里面、self.xxx ...实例变量否一句话带走实例变量写__init__里带self.别写在 class 下面坑点 9setdefaultvsget——看起来一样结果天差地别 [进阶]毛毛姐排查了半天——两个函数返回值一模一样但有一个偷偷改了字典。# get只读不修改原字典d{}resultd.get(key,[])result.append(1)print(d)# {}# setdefault读 写把默认值写入原字典d{}resultd.setdefault(key,[])result.append(1)print(d)# {key: [1]} ← 被改了一句话带走get只读不写setdefault会偷偷改字典根因篇一切源于标签思维 [深入]晚上毛毛姐发来一条特别认真的消息。毛毛姐「今天踩了九个坑你每次都说是’引用’的问题。能不能系统讲一下——为什么 Python 跟别的语言不一样」核心变量不是盒子是标签C 语言里变量是装着值的盒子。Python 里变量是贴在对象上的标签x5# 往整数对象 5 上贴一张标签叫 xx10# 把 x 标签撕下来贴到新的整数对象 10 上这解释了今天所有的问题毛毛姐写的Python 实际做的后果a b []两张标签贴到同一个列表对象改 a 等于改 b[[]] * 3三张标签都贴到同一个空列表改一个全变def f(x[])[]作为f.__defaults__[0]存起来每次调用用同一个copy.copy(nested)外层新对象内层标签原样复制内层共享class C: items []类变量是所有实例共享的标签成绩单串了回到坑点 1 的__defaults__——它在def时就创建好了所有调用都从同一个储物柜取列表。改了内容下次取出来的自然也是改过的。毛毛姐「所以整个系列的核心思想就是——Python 里变量是标签赋值是贴标签改对象是所有标签都能看到的。」我「……你总结得比我好。」一句话带走Python 里变量是标签不是盒子。改之前问自己——有几个标签在指着这个对象毛毛姐的踩坑日记 · 本期总结#坑一句话领悟1可变默认参数默认值在def时就创建了用None做哨兵2列表乘法*n是复制引用不是复制数据3循环删元素边删边遍历索引会乱用列表推导式4赋值不是复制赋值是贴标签不是复制5浅拷贝 vs 深拷贝一层用copy()嵌套用deepcopy()6行为不一致列表就地改元组建新的7元组不可变元组锁的是引用不是内容8类变量共享实例变量写__init__里带self.9setdefaultvsget一个只读一个有写副作用10根因标签思维变量不是盒子是标签三条黄金法则默认值别用会被修改的可变对象——None做哨兵别用[]{}set()需要独立对象就显式创建——列表推导式、deepcopy、__init__里初始化动手前先数标签——改之前问自己有几个变量在指着这个对象毛毛姐「今天大脑 CPU 温度 98 度。明天再去拍视频——标题就叫’学 Python 第一天函数居然自己长记性’。」我「你学 Python 到底是为了写代码还是拍视频」毛毛姐「两手都要抓两手都要硬。拜拜」 系列预告下一期毛毛姐写了个 for 循环生成 lambda 函数全都返回同一个值——“Python 的记忆力是不是太好了” 如果你身边也有一个写 Python 的朋友把这篇文章转给 ta——「你看你写的那个 bug毛毛姐也踩过。」
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。