CC++ & Algorithm

Python参数传递方式

较难0
语言版本:C++
概述:Python传递参数时,不可变类型像复印照片,函数内改不了原值;可变类型像共享同一个乐高,函数内会影响到原对象。

Python参数传递方式:你的数据在函数里会变吗?

在编程中,我们经常把数据交给函数去处理。但是,函数里的操作会不会改变原来的数据呢?答案取决于数据的类型——有些数据像照片,复印一份给朋友,朋友在复印件上画画,原件不受影响;有些数据像橡皮泥,你递给朋友一块,他捏一捏,你手里的那块也跟着变形了。理解这一点,就能避免很多“数据莫名其妙被改了”的错误。

生活中的传递方式

想象两种玩具:

  • 照片:画面固定,不能修改。如果你把照片递给朋友看,朋友在照片上画东西,会改变你手里的照片吗?不会,因为照片本身不可改变。他只能另拿一张纸来画。
  • 橡皮泥:可以随意揉捏。如果你把橡皮泥给朋友,他捏成小动物,你手里的橡皮泥就变成了小动物——因为你们共享的是同一块泥。

Python 里的数据也分这两种“性格”。

Python中的“可变”与“不可变”

Python 的数据类型按“能不能修改内部内容”分为两类:

不可变类型(像照片)可变类型(像橡皮泥)
整数 (int)列表 (list)
浮点数 (float)字典 (dict)
字符串 (str)集合 (set)
元组 (tuple)
  • 不可变类型:一旦创建,值就不能改变。比如 a = 5,你没法把数字 5 本身改成 6,只能让 a 指向另一个数字 6
  • 可变类型:可以增加、删除、修改内部元素。比如 lst = [1,2],你可以 lst.append(3),列表本身变了,但变量名 lst 仍然指向同一个列表。

函数参数传递的本质

当函数被调用时,Python 会把对象的引用(可以理解为“指向对象的地址”)复制一份传给形参。注意:是复制引用,而不是复制数据本身。

对于不可变类型,形参和实参最初指向同一个对象。但如果函数内试图“修改”这个对象(比如拼接字符串),由于对象不可变,Python 会创建一个新对象,形参转而指向新对象,而实参仍然指向旧对象——就像你复印了照片,在复印件上画画,原件不变。

对于可变类型,形参和实参也指向同一个对象。函数内如果修改对象的内容(比如添加列表元素),因为修改的是对象本身,形参和实参看到的是同一个变化——就像你俩捏同一块橡皮泥。

不可变类型参数传递演示

字符串例子(保留原例并扩充)

def change_string(s):
    # s 是形参,最初指向和实参相同的字符串
    s = s + "世界"          # 拼接产生新字符串,s 指向新对象
    print("函数内:", s)

text = "你好"               # 原始字符串
change_string(text)         # 传递"你好"的引用
print("函数外:", text)      # 原始字符串没变,还是"你好"

运行结果:

函数内: 你好世界
函数外: 你好

整数例子(补充)

def add_ten(num):
    # num 最初指向和实参相同的整数
    num = num + 10          # 加法产生新整数,num 指向新对象
    print("函数内:", num)

score = 85                  # 原始分数
add_ten(score)              # 传递85的引用
print("函数外:", score)     # 分数没变,还是85

运行结果:

函数内: 95
函数外: 85

元组例子(补充)

元组也是不可变的,函数内无法修改其元素,但可以重新赋值形参。

def try_change_tuple(t):
    # t 指向原始元组
    # t[0] = 99  # 这行会报错!元组不可修改
    t = (100, 200)          # 创建新元组,t 指向新对象
    print("函数内:", t)

my_tuple = (1, 2, 3)        # 原始元组
try_change_tuple(my_tuple)
print("函数外:", my_tuple)  # 没变

可变类型参数传递演示

列表例子(保留原例并扩充)

def add_element(lst):
    # lst 指向和实参相同的列表对象
    lst.append(4)           # 直接在原列表末尾添加元素
    print("函数内:", lst)

my_list = [1, 2, 3]         # 原始列表
add_element(my_list)        # 传递列表的引用
print("函数外:", my_list)   # 列表内容变了,多了一个4

运行结果:

函数内: [1, 2, 3, 4]
函数外: [1, 2, 3, 4]

字典例子(补充)

def update_score(dic):
    # dic 指向和实参相同的字典
    dic["math"] = 100       # 修改字典中的值
    print("函数内:", dic)

my_scores = {"math": 90, "english": 85}   # 原始字典
update_score(my_scores)
print("函数外:", my_scores)               # 数学成绩变成了100

运行结果:

函数内: {'math': 100, 'english': 85}
函数外: {'math': 100, 'english': 85}

新手容易犯的错误

错误1:以为函数内修改列表会复制一份

很多同学写代码时,希望函数不改变原列表,却直接传入了列表,结果原列表被改了。

def remove_last(lst):
    lst.pop()         # 直接修改原列表

fruits = ["苹果", "香蕉", "橘子"]
remove_last(fruits)   # 原列表少了最后一个
print(fruits)         # ['苹果', '香蕉']   “橘子”不见了!

解决:如果想保留原列表,就传一份副本。

错误2:混淆“赋值”和“修改”

对可变类型,形参 = 新值 是让形参指向一个新对象(不影响原对象),而 形参.方法()形参[索引]=值 是修改对象本身(影响原对象)。

def wrong(lst):
    lst = [100, 200]      # 形参指向新列表,原列表不变

def right(lst):
    lst[0] = 100          # 修改原列表的第一个元素

data = [1, 2, 3]
wrong(data)
print(data)               # [1, 2, 3] 没变(因为wrong里是赋值)
right(data)
print(data)               # [100, 2, 3] 变了(因为修改了元素)

如何避免意外修改(传递副本的方法)

如果你不希望函数改变原来的可变数据,可以在调用时传一个副本。常用的方法:

1. 列表用 copy() 或切片 [:]

def add_element_safe(lst):
    lst = lst.copy()      # 复制一份,后续修改对原列表无影响
    lst.append(99)
    print("函数内:", lst)

original = [1, 2, 3]
add_element_safe(original)
print("函数外:", original)   # [1, 2, 3] 安全

2. 字典用 copy()

def update_score_safe(dic):
    dic = dic.copy()      # 复制一份
    dic["math"] = 100
    print("函数内:", dic)

scores = {"math": 80}
update_score_safe(scores)
print("函数外:", scores)    # {"math": 80} 没变

3. 使用 copy 模块的 deepcopy(深拷贝,适用于嵌套数据结构)

import copy

nested = [[1, 2], [3, 4]]
new = copy.deepcopy(nested)   # 完全独立的一份
new[0].append(99)
print(nested)                  # [[1, 2], [3, 4]] 不受影响

完整可运行示例(综合演示)

下面这段代码把所有情况放在一起,你可以复制到 Python 环境里运行,观察变化。

# 完整演示:参数传递方式
def demo_immutable(num, text, tup):
    # 不可变类型的形参修改
    num = num + 1                     # 产生新整数
    text = text + "(已处理)"        # 产生新字符串
    # tup[0] = 99                    # 会报错,元组不可修改
    tup = (100, 200)                 # 产生新元组
    print("  函数内 num:", num)
    print("  函数内 text:", text)
    print("  函数内 tup:", tup)

def demo_mutable(lst, dic, st):
    # 可变类型的形参修改
    lst.append("末尾")               # 修改原列表
    dic["new_key"] = "新值"          # 修改原字典
    st.add("新元素")                 # 修改原集合
    print("  函数内 lst:", lst)
    print("  函数内 dic:", dic)
    print("  函数内 st:", st)

# ---------- 测试不可变类型 ----------
my_num = 10              # 原始整数
my_text = "Hello"        # 原始字符串
my_tup = (1, 2, 3)       # 原始元组

print("调用前:")
print("  num:", my_num)
print("  text:", my_text)
print("  tup:", my_tup)

demo_immutable(my_num, my_text, my_tup)

print("调用后(不可变类型不受影响):")
print("  num:", my_num)      # 仍是10
print("  text:", my_text)    # 仍是Hello
print("  tup:", my_tup)      # 仍是(1,2,3)

print("-" * 30)

# ---------- 测试可变类型 ----------
my_list = [1, 2]         # 原始列表
my_dict = {"a": 1}       # 原始字典
my_set = {10, 20}        # 原始集合

print("调用前:")
print("  lst:", my_list)
print("  dic:", my_dict)
print("  st:", my_set)

demo_mutable(my_list, my_dict, my_set)

print("调用后(可变类型被修改):")
print("  lst:", my_list)      # [1, 2, '末尾']
print("  dic:", my_dict)      # {'a': 1, 'new_key': '新值'}
print("  st:", my_set)        # {10, 20, '新元素'}

相关知识点指引

  • 列表的常见操作appendpopinsert 等会修改原列表;sorted(lst) 返回新列表,不修改原列表。
  • 函数的返回值:如果你既想修改数据,又想保留原数据,可以在函数内创建副本,处理完再返回新值。
  • 全局变量:如果在函数内修改全局的可变对象,也会影响全局,但通常不推荐这样做。
  • copy 模块:用于浅拷贝(copy.copy)和深拷贝(copy.deepcopy),处理嵌套数据结构时很有用。

记住一句话:不可变类型像照片,函数内改不了原件;可变类型像共享的橡皮泥,函数内一变,外面跟着变。写代码时多留个心眼,必要时传个副本,就能安全地使用函数了。

例题精讲

1单选题

下列代码执行后,输出结果是? def func(x): x += 1 return x a = 10 func(a) print(a)

A10
B11
C报错
D未定义
2单选题

下列代码执行后,输出结果是? def modify(lst): lst.append(4) lst = [5, 6] return my_list = [1, 2, 3] modify(my_list) print(my_list)

A[1, 2, 3, 4]
B[1, 2, 3]
C[5, 6]
D[1, 2, 3, 4, 5, 6]
3判断题

在Python中,如果函数参数是一个字典,函数内部修改字典的键值对(如 d['key']='new'),则函数外部对应的原字典也会发生改变。

4填空题
执行以下代码,输出结果为______。
def change(s):
    s = s + ' world'
    return s
text = 'hello'
change(text)
print(text)
5填空题
执行以下代码,输出结果为______。
def add_item(lst, item):
    lst = lst + [item]
    return lst
my_list = [1, 2]
add_item(my_list, 3)
print(my_list)