Python不可变类型参数传递
中等4为什么函数里改不了外面的数字?——Python不可变类型参数传递详解
当你写代码时,是不是遇到过这样的情况:你辛辛苦苦把考试成绩变量传给函数,想让函数帮忙加10分,结果函数里倒是加上了,外面的分数却一动不动?别急,这不是你的代码有bug,而是Python的“不可变类型参数传递”在起作用。简单说,如果传给函数的是数字、字符串、元组这类“不能变”的东西,函数里不管怎么改,都不会影响外面的变量——就像你给老师交了一张试卷复印件,老师在上面改分,原件上的分数纹丝不动。
什么是不可变类型?
在Python里,有些类型天生“死心眼”,一旦创建,里面的值就不能再改变了。比如:
- 整数:
5永远就是5,你不能把5改成6,只能重新创建一个值为6的新数字。 - 浮点数:
3.14同理。 - 字符串:
"hello"你没法把h改成H而不生成新字符串。 - 元组:
(1, 2, 3)里的元素不能增删改。 - 布尔值:
True和False也是固定不变的。
这些类型统称为不可变类型。记住它们,因为参数传递的秘密就藏在这里。
参数传递:是“给复印件”还是“给了原件”?
当你调用函数时,把变量名传给函数,Python背后做了什么?很多同学以为是把变量本身“递”进去,其实不是——Python把变量指向的那个对象,复制一份引用(或者说“指向”)给了函数。对于不可变类型的对象,这个复制的过程就像给了你一个复印件:函数里对这个副本的任何修改,都不会动到原件。
生活里找比喻:三组场景帮你记牢
场景1:零花钱账单
你有一张写着自己零花钱的纸条,上面写着“50元”。你把纸条的复印件给妈妈,妈妈在复印件上写着“买了零食,还剩40元”。你手里的原件还是“50元”,一分没少。这就是数字、字符串传递的效果。
场景2:考试成绩单
考试得了95分,你把分数(整数)传给一个叫 add_bonus 的函数,想让它加5分。函数内部新建了一个100分的“假成绩单”,并打印出来。但你手里的原始成绩单还是95分,加分的愿望落空了。
场景3:借书时的书名
你想推荐一本好书,把书名“哈利波特”告诉朋友(字符串传递)。朋友在纸上把书名改成“西游记”,但你自己记在脑子里的书名还是“哈利波特”。因为朋友改的是他的记录,你的记录是独立的。
看代码就懂了(保留原有例子,并补充)
原有例子非常清晰,我们再看一遍,并补充更多情况:
def try_to_change(number):
# number 接收到的是 my_number 指向的5的一份“复印件”
number = number + 10 # 这里新建了值为15的对象,让number指向它
print("函数内部:", number) # 输出 15
my_number = 5 # my_number 指向整数对象5
try_to_change(my_number)
print("函数外部:", my_number) # 输出 5,原来的没变!
再试试字符串:
def change_name(name):
name = name + "同学" # 新建字符串,name指向新字符串
print("函数内部名字:", name)
student_name = "小明" # 变量指向字符串"小明"
change_name(student_name)
print("函数外部名字:", student_name) # 还是"小明",没变成"小明同学"
元组也一样:
def try_modify_tuple(t):
# 元组不可变,无法用 t[0] = 100 直接改,只能重新赋值
t = (100, 200, 300) # 新建一个元组
print("函数内部元组:", t)
my_tuple = (1, 2, 3)
try_modify_tuple(my_tuple)
print("函数外部元组:", my_tuple) # 仍然是 (1, 2, 3)
为什么会这样?——背后的内存小故事
当 Python 执行 number = 5 时,它在内存里创建了一个“5”的盒子,然后让变量名 my_number 指向这个盒子。接着调用函数时,参数 number 也拿到了指向同一个盒子的“钥匙”。但是!当你在函数里写 number = number + 10 时,number + 10 先计算出一个新值 15,然后 Python 新建了一个“15”的盒子,让 number 指向这个新盒子。而外部的 my_number 依然指向原来的“5”盒子。所以两者从此分道扬镳,互不影响。
关键点:不可变类型不允许“修改”对象本身,只能创建新对象。所以任何“看起来像修改”的操作(如 +、字符串拼接、元组拼接),实际上都是生成新对象,并让参数指向新对象。外面的变量还是指着老对象。
新手容易犯的错误
错误1:以为函数能“偷改”外面的整数
def add_score(total):
total = total + 10 # 这里没有返回,外部的total不变
print("内部加后:", total)
my_score = 80
add_score(my_score)
print("外部:", my_score) # 还是80,你以为分数加了,其实没加
正确做法:要让外部变量改变,需要把新值用 return 返回并赋值:
def add_score(total):
total = total + 10
return total
my_score = 80
my_score = add_score(my_score) # 把新值存回原变量
print("外部:", my_score) # 现在输出90
错误2:混淆了“修改参数”和“修改参数指向的对象”
对于不可变类型,你永远无法修改对象,只能让参数指向新对象。但对于可变类型(列表、字典),你可以修改对象内部(如 list.append()),那会影响到外部。很多初学者分不清,以为所有类型都一样。记住:不可变类型传参 = 复印件,可变类型传参 = 共享原件。
错误3:在函数里用默认参数时用了可变类型
虽然这不完全属于不可变类型参数传递,但也是常见坑:默认参数如果设为空列表 [](可变类型),多次调用会累积数据。建议默认参数用 None 加判断,或者只用不可变类型做默认值。
完整可运行的示例
下面是一个完整的例子,包含整数、字符串、元组三种不可变类型的传递,并展示如何用 return 让外部变量得到新值:
# 演示不可变类型参数传递
def change_number(num):
num = num * 2 # 新建对象,num指向新数字
print("函数内部数字:", num) # 输出新数字
return num # 返回新值
def change_string(text):
text = text + "!" # 新建字符串
print("函数内部字符串:", text)
return text
def change_tuple(tup):
tup = tup + (100,) # 新建元组(注意逗号)
print("函数内部元组:", tup)
return tup
# 主程序
my_num = 5 # 定义变量:数字
my_str = "你好" # 定义变量:字符串
my_tup = (1, 2, 3) # 定义变量:元组
print("调用前:", my_num, my_str, my_tup)
# 调用函数,但不接收返回值
change_number(my_num)
change_string(my_str)
change_tuple(my_tup)
print("不接收返回值,外部没变:", my_num, my_str, my_tup)
# 正确做法:接收返回值并赋值回原变量
my_num = change_number(my_num)
my_str = change_string(my_str)
my_tup = change_tuple(my_tup)
print("接收返回值后,外部全变了:", my_num, my_str, my_tup)
运行结果:
调用前:5 你好 (1, 2, 3)
函数内部数字: 10
函数内部字符串: 你好!
函数内部元组: (1, 2, 3, 100)
不接收返回值,外部没变: 5 你好 (1, 2, 3)
函数内部数字: 10
函数内部字符串: 你好!
函数内部元组: (1, 2, 3, 100)
接收返回值后,外部全变了: 10 你好! (1, 2, 3, 100)
相关指引
掌握了不可变类型参数传递,你一定想知道“那可变类型(列表、字典)传给函数会怎么样?”——答案刚好相反:函数内修改会直接影响外部,因为它们共享同一个对象。去看看 Python可变类型参数传递(标识符: mutable-passing-py)吧,那里有“共享白板”的生动例子。
另外,如果你想了解怎么在函数里给参数设置“默认值”,或者调用时通过名字指定参数,可以学习 Python默认参数与关键字参数(标识符: default-keyword-args-py),让函数用起来更灵活。
总之,记住这个口诀:“不可变传复印件,可变传原件;改了复印件,原件不变;改了原件,大家全变。” 把它贴在电脑前,写函数参数时就不容易迷路了!
例题精讲
在Python中,有一个函数定义如下: def change(x): x = 10 a = 5 change(a) print(a) 以上代码的输出结果是?
在Python函数中,如果参数是字符串(不可变类型),函数内部对字符串进行修改(如使用“+=”),外部变量也会随之改变。
运行以下代码,输出结果是___。
def test(t):
t = t + (4,)
a = (1, 2, 3)
test(a)
print(a)下列哪个数据类型作为函数参数时,在函数内部修改(如添加或删除元素)会影响外部变量?
运行以下代码,输出结果是___。
def change(s):
s = s + " world"
a = "hello"
change(a)
print(a)