Python参数传递方式
较难0Python参数传递方式:你的数据在函数里会变吗?
在编程中,我们经常把数据交给函数去处理。但是,函数里的操作会不会改变原来的数据呢?答案取决于数据的类型——有些数据像照片,复印一份给朋友,朋友在复印件上画画,原件不受影响;有些数据像橡皮泥,你递给朋友一块,他捏一捏,你手里的那块也跟着变形了。理解这一点,就能避免很多“数据莫名其妙被改了”的错误。
生活中的传递方式
想象两种玩具:
- 照片:画面固定,不能修改。如果你把照片递给朋友看,朋友在照片上画东西,会改变你手里的照片吗?不会,因为照片本身不可改变。他只能另拿一张纸来画。
- 橡皮泥:可以随意揉捏。如果你把橡皮泥给朋友,他捏成小动物,你手里的橡皮泥就变成了小动物——因为你们共享的是同一块泥。
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, '新元素'}
相关知识点指引
- 列表的常见操作:
append、pop、insert等会修改原列表;sorted(lst)返回新列表,不修改原列表。 - 函数的返回值:如果你既想修改数据,又想保留原数据,可以在函数内创建副本,处理完再返回新值。
- 全局变量:如果在函数内修改全局的可变对象,也会影响全局,但通常不推荐这样做。
copy模块:用于浅拷贝(copy.copy)和深拷贝(copy.deepcopy),处理嵌套数据结构时很有用。
记住一句话:不可变类型像照片,函数内改不了原件;可变类型像共享的橡皮泥,函数内一变,外面跟着变。写代码时多留个心眼,必要时传个副本,就能安全地使用函数了。
例题精讲
下列代码执行后,输出结果是? def func(x): x += 1 return x a = 10 func(a) print(a)
下列代码执行后,输出结果是? def modify(lst): lst.append(4) lst = [5, 6] return my_list = [1, 2, 3] modify(my_list) print(my_list)
在Python中,如果函数参数是一个字典,函数内部修改字典的键值对(如 d['key']='new'),则函数外部对应的原字典也会发生改变。
执行以下代码,输出结果为______。
def change(s):
s = s + ' world'
return s
text = 'hello'
change(text)
print(text)执行以下代码,输出结果为______。
def add_item(lst, item):
lst = lst + [item]
return lst
my_list = [1, 2]
add_item(my_list, 3)
print(my_list)