CC++ & Algorithm

Python不可变类型参数传递

中等4
语言版本:C++Python
概述:就像把饭卡复印件给食堂阿姨,你卡里的钱不会变——传递不可变类型时,函数内部修改不影响外部变量。

为什么函数里改不了外面的数字?——Python不可变类型参数传递详解

当你写代码时,是不是遇到过这样的情况:你辛辛苦苦把考试成绩变量传给函数,想让函数帮忙加10分,结果函数里倒是加上了,外面的分数却一动不动?别急,这不是你的代码有bug,而是Python的“不可变类型参数传递”在起作用。简单说,如果传给函数的是数字、字符串、元组这类“不能变”的东西,函数里不管怎么改,都不会影响外面的变量——就像你给老师交了一张试卷复印件,老师在上面改分,原件上的分数纹丝不动。

什么是不可变类型?

在Python里,有些类型天生“死心眼”,一旦创建,里面的值就不能再改变了。比如:

  • 整数5 永远就是5,你不能把 5 改成 6,只能重新创建一个值为6的新数字。
  • 浮点数3.14 同理。
  • 字符串"hello" 你没法把 h 改成 H 而不生成新字符串。
  • 元组(1, 2, 3) 里的元素不能增删改。
  • 布尔值TrueFalse 也是固定不变的。

这些类型统称为不可变类型。记住它们,因为参数传递的秘密就藏在这里。

参数传递:是“给复印件”还是“给了原件”?

当你调用函数时,把变量名传给函数,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),让函数用起来更灵活。

总之,记住这个口诀:“不可变传复印件,可变传原件;改了复印件,原件不变;改了原件,大家全变。” 把它贴在电脑前,写函数参数时就不容易迷路了!

例题精讲

1单选题

在Python中,有一个函数定义如下: def change(x): x = 10 a = 5 change(a) print(a) 以上代码的输出结果是?

A5
B10
CNone
D报错
2判断题

在Python函数中,如果参数是字符串(不可变类型),函数内部对字符串进行修改(如使用“+=”),外部变量也会随之改变。

3填空题
运行以下代码,输出结果是___。

def test(t):
    t = t + (4,)
a = (1, 2, 3)
test(a)
print(a)
4单选题

下列哪个数据类型作为函数参数时,在函数内部修改(如添加或删除元素)会影响外部变量?

Aint
Bstr
Ctuple
Dlist
5填空题
运行以下代码,输出结果是___。

def change(s):
    s = s + " world"
a = "hello"
change(a)
print(a)