Python可变类型参数传递
困难4房间、门牌号与你的零花钱:Python 可变类型参数传递
小朋友,你有没有遇到过这种情况:你有一张写着小伙伴名字的纸条,你把纸条递给另一个人,让他按纸条上的名字去找人。如果他把纸条上的名字改了,那你手上的纸条也会跟着变吗?在 Python 里,函数参数传递就像送纸条,但“可变类型”和“不可变类型”的规矩不一样。今天我们就用一个更形象的比喻——房间与门牌号,来搞懂为什么在函数里修改列表、字典等可变类型会改变原始数据。
什么是“房间”和“门牌号”?
想象一下,你家有一个大房间(叫作 my_room),里面摆着你的玩具、书本和零食。这个房间有一个门牌号(比如“302”)。如果你告诉朋友:“我的房间在 302,你直接进去帮我拿个薯片吧。”朋友拿着门牌号找到房间,开门进去,拿走薯片——你的房间里的薯片确实少了,因为朋友是在你的房间里 直接操作 的。
在 Python 中,变量名就相当于门牌号,而 数据(列表、字典等)就是房间里的东西。当你把一个变量传给函数时,Python 传递的不是整个房间的“照片”,而是 门牌号。函数拿到门牌号后,可以直接走进这个房间,换掉里面的家具。
什么是可变类型?什么是不可变类型?
- 可变类型:像可以随时换家具的房间。比如列表(list)、字典(dict)、集合(set)——房间里的东西可以增减、替换。
- 不可变类型:像一个锁死的盒子。比如整数(int)、字符串(str)、元组(tuple)——里面的东西不能直接改,只能整个换新盒子。
举个例子:
- 列表:
[1, 2, 3]可以变成[1, 2, 3, 4],这是修改房间里的物品。 - 整数:
5不能变成6,你只能把门牌号重新指向一个新盒子6,原来的盒子5还在原地。
参数传递的规矩:门牌号 vs 复印的号码
1. 传递可变类型(列表、字典)——给的是真门牌号
当你把列表 numbers 传给函数时,函数拿到了这个列表的 门牌号。函数里写 my_list.append(4),就相当于朋友拿着门牌号走进房间,往房间里摆了一把新椅子。你原来的 numbers 房间当然也多了这把椅子。
def add_to_list(my_list):
my_list.append(4) # 在房间里放一把新椅子
numbers = [1, 2, 3] # 给房间起名叫 numbers,里面有三件物品
add_to_list(numbers) # 把门牌号送给函数
print(numbers) # 输出 [1, 2, 3, 4] 房间里的椅子变多了!
2. 传递不可变类型(整数、字符串、元组)——给的是复印的号码
如果你给朋友一张纸,上面写着“302”,但朋友把这张纸涂改成“401”,你手上的纸条会变吗?不会!因为朋友改的是复印件,不是你的原版。
对于不可变类型,比如整数,Python 在函数内部会把参数变量重新指向一个新盒子,而外面的变量还指向原来的盒子。
def add_one(num):
num = num + 1 # 朋友把门牌号改成了另一个新房间的号码
x = 5 # 你的零花钱是 5 元
add_one(x) # 你让函数帮你去加 1 元
print(x) # 输出 5,你的零花钱没变!朋友只改了他手里的复印件
3. 生活中的另一个例子:你的游戏装备
假设你有一个 backpack 列表,里面装着你在游戏里捡到的装备:
def add_sword(bag):
bag.append("铁剑") # 往背包里放一把铁剑
backpack = ["木棍", "药水"] # 初始装备
add_sword(backpack)
print(backpack) # 输出 ['木棍', '药水', '铁剑'] 背包真的多了铁剑
而如果你的 gold 是整数(金币数量):
def buy_potion(money):
money = money - 10 # 模拟花掉 10 金币(实际上创建了新变量)
gold = 100 # 你有 100 金币
buy_potion(gold)
print(gold) # 输出 100,你的金币没少!因为函数里改的是复印件
新手最容易犯的 3 个错误
❌ 错误 1:以为修改列表不会影响外面
def clear_list(lst):
lst = [] # 这里不是修改房间,而是把门牌号指向了一个新房间
my_list = [1, 2, 3]
clear_list(my_list)
print(my_list) # 输出 [1, 2, 3] 原列表没变!
原因:lst = [] 只是让函数里的 lst 变量指向一个新的空列表,并没有修改原来的房间。要清空列表应该用 lst.clear() 或 lst[:] = []。
❌ 错误 2:在函数内修改字典后,外面也跟着变,但你不想要这个效果
比如你想写一个函数,给同学的成绩单加上奖励分,但又不想改原始数据:
scores = {"小明": 80, "小红": 90}
def add_bonus(score_dict):
for name in score_dict:
score_dict[name] += 5 # 直接修改了原始字典
add_bonus(scores)
print(scores) # 输出 {'小明': 85, '小红': 95} 原始数据被改了!
如果你不想改原字典,可以先复制一份(用 .copy() 或 copy 模块)。
❌ 错误 3:混淆“修改对象”和“重新赋值”
list.append(4)→ 修改房间内部(可变类型操作)num = num + 1→ 让变量指向新房间(不可变类型操作)
记住:只有当你真正走进房间动家具时,外面的房间才会变;如果你只是换了一张门牌号,外面的门牌号不变。
完整可运行的示例:班费与零食基金
下面是一个模拟班费管理的完整程序,帮助你巩固理解:
# 可变类型:班费支出列表
def spend_money(expense_list, amount):
expense_list.append(amount) # 记录一笔支出,直接在原列表添加
# 不可变类型:结余金额
def update_balance(balance, amount):
balance = balance - amount # 这里只是重新赋值,外面的 balance 不变
return balance # 需要返回新值才能生效
# 主程序
class_fund = [100] # 初始班费:100 元,用一个列表装(可变)
balance = 100 # 余额用整数表示(不可变)
print("原始数据:")
print(" 班费记录:", class_fund) # 输出 [100]
print(" 余额:", balance) # 输出 100
# 支出了 20 元买零食
spend_money(class_fund, 20) # 在列表中增加一笔 20 元支出
balance = update_balance(balance, 20) # 必须赋值回来,余额才更新
print("\n支出后:")
print(" 班费记录:", class_fund) # 输出 [100, 20] 列表被修改了!
print(" 余额:", balance) # 输出 80 余额也更新了
注意最后一行 balance = update_balance(balance, 20),因为整数不可变,我们只能 返回新值并重新赋值。而列表可以省去这一步,因为函数直接修改了原房间。
为什么要知道这个?
如果你在函数里改了列表,而外面还指望它保持原样,程序就会出 bug。比如:
- 你想让函数只查看你的零食库存,结果函数却偷偷吃了你的零食。
- 你想让函数复制全班同学的成绩单,结果函数把原始成绩单也改了。
明白这个区别后,你就能根据需求选择:
- 想要函数 影响外部数据 → 直接传可变类型,函数内修改。
- 想要函数 不改变外部数据 → 先拷贝再修改,或者使用不可变类型。
相关指引
学完这个知识点,你可以继续探索:
- 深拷贝与浅拷贝(
copy.deepcopyvscopy.copy)—— 当列表中嵌套列表时,拷贝也分“轻微”和“彻底”。 - 函数式编程中的不可变思想—— 很多高级语言鼓励尽量用不可变数据,减少 bug。
- Python 中的可变与不可变类型全览—— 除了 list、dict、set,还有哪些?
- 参数的默认值陷阱—— 如果你的函数参数默认值是
[],多次调用会发生奇怪的事。
掌握了参数传递的“房间与门牌号”原理,你就能在写函数时心里有数,不再被“变量改没改”搞晕啦!
例题精讲
已知函数定义:def modify(lst): lst.append(4),执行以下代码:a = [1, 2, 3]; modify(a); print(a) 的输出结果是什么?
执行以下代码:def change(x): x = [5,6]; y = [1,2]; change(y); print(y) 输出为[5,6]。
下面代码中,函数add_item使用可变默认参数,多次调用后输出结果是什么?请填写输出结果。
def add_item(item, lst=[]):
lst.append(item)
return lst
print(add_item(1))
print(add_item(2))下列哪个选项正确地描述了Python中可变类型参数传递的特点?
执行以下代码:def update(d): d['a'] = 100; d = {}; data = {'a':1}; update(data); print(data) 输出为{'a':100}。