26. Python函数值传递和引用传递详解
一、Python函数基础知识
1. 什么是函数?
函数是Python中可重复使用的代码块,它接收输入(参数),执行特定任务,并可能返回结果。函数帮助我们组织代码,提高代码复用性和可维护性。
2. 函数的定义和调用
代码语言:javascript代码运行次数:0运行复制# 定义函数
def 打招呼(名字):
"""这是函数的文档字符串,用于说明函数功能"""
return f"你好,{名字}!"
# 调用函数
结果 = 打招呼("小明")
print(结果) # 输出:你好,小明!
3. 函数参数的类型
代码语言:javascript代码运行次数:0运行复制# 位置参数
def 计算矩形面积(长, 宽):
return 长 * 宽
print(计算矩形面积(5, 3)) # 输出:15
# 默认参数
def 打招呼(名字, 问候语="你好"):
return f"{问候语},{名字}!"
print(打招呼("小明")) # 输出:你好,小明!
print(打招呼("小红", "早上好")) # 输出:早上好,小红!
# 关键字参数
print(计算矩形面积(宽=3, 长=5)) # 输出:15
# 可变参数
def 计算总和(*数字):
return sum(数字)
print(计算总和(1, 2, 3, 4, 5)) # 输出:15
# 关键字可变参数
def 创建个人信息(**信息):
return 信息
个人资料 = 创建个人信息(姓名="小明", 年龄=18, 城市="北京")
print(个人资料) # 输出:{'姓名': '小明', '年龄': 18, '城市': '北京'}
二、形参与实参
在讨论函数参数传递机制前,我们需要理解两个重要概念:
1. 形参(Parameter)
形参是函数定义时声明的变量,它在函数定义时创建,函数结束时销毁。
2. 实参(Argument)
实参是函数调用时传递给函数的实际值,它在函数调用前就已经存在。
代码语言:javascript代码运行次数:0运行复制def 打招呼(名字): # 这里的"名字"是形参
return f"你好,{名字}!"
人名 = "小明" # 这是一个变量
结果 = 打招呼(人名) # 这里的"人名"是实参
三、Python的参数传递机制
1. Python的对象模型
在Python中,一切皆为对象。每个对象都有三个特性:
- 标识(Identity):对象在内存中的地址,可通过
id()
函数获取 - 类型(Type):对象的类型,决定了对象可以进行的操作,可通过
type()
函数获取 - 值(Value):对象包含的数据
2. 可变对象与不可变对象
Python中的对象分为两类:
不可变对象(Immutable):
- 整数(int)
- 浮点数(float)
- 字符串(str)
- 元组(tuple)
- 布尔值(bool)
可变对象(Mutable):
- 列表(list)
- 字典(dict)
- 集合(set)
这个区别对理解参数传递机制至关重要!
3. Python的参数传递机制:对象引用传递
Python中的参数传递机制实际上是对象引用传递(Pass by Object Reference)。这意味着:
- 函数参数在传递时,传递的是对象的引用(内存地址)
- 对于不可变对象,如果在函数内修改参数的值,会创建一个新对象
- 对于可变对象,在函数内的修改会直接影响原对象
四、不可变对象的参数传递(看起来像值传递)
当我们传递不可变对象(如整数、字符串)作为参数时,由于这些对象不能被修改,所以在函数内对参数的修改不会影响到原始对象。这种行为看起来像是值传递。
代码语言:javascript代码运行次数:0运行复制def 修改数字(数值):
print(f"函数内修改前,数值的ID:{id(数值)}")
数值 += 10 # 这会创建一个新对象!
print(f"函数内修改后,数值的ID:{id(数值)}")
return 数值
原始数字 = 5
print(f"函数调用前,原始数字的ID:{id(原始数字)}")
结果 = 修改数字(原始数字)
print(f"函数调用后,原始数字:{原始数字}")
print(f"函数调用后,结果:{结果}")
print(f"函数调用后,原始数字的ID:{id(原始数字)}")
print(f"函数调用后,结果的ID:{id(结果)}")
输出结果类似:
代码语言:javascript代码运行次数:0运行复制函数调用前,原始数字的ID:140704726601296
函数内修改前,数值的ID:140704726601296
函数内修改后,数值的ID:140704726601616
函数调用后,原始数字:5
函数调用后,结果:15
函数调用后,原始数字的ID:140704726601296
函数调用后,结果的ID:140704726601616
解释:
- 原始数字和函数参数最初指向同一个对象(ID相同)
- 当我们在函数内执行
数值 += 10
时,由于整数是不可变的,Python创建了一个新的整数对象 - 函数返回的是新创建的对象
- 原始数字保持不变
字符串示例
代码语言:javascript代码运行次数:0运行复制def 修改字符串(文本):
print(f"函数内修改前,文本的ID:{id(文本)}")
文本 += "世界" # 创建新字符串对象
print(f"函数内修改后,文本的ID:{id(文本)}")
return 文本
原始文本 = "你好"
print(f"函数调用前,原始文本的ID:{id(原始文本)}")
结果 = 修改字符串(原始文本)
print(f"函数调用后,原始文本:{原始文本}")
print(f"函数调用后,结果:{结果}")
五、可变对象的参数传递(看起来像引用传递)
当我们传递可变对象(如列表、字典)作为参数时,函数内对参数的修改会直接影响原始对象。这种行为看起来像是引用传递。
代码语言:javascript代码运行次数:0运行复制def 修改列表(列表):
print(f"函数内修改前,列表的ID:{id(列表)}")
列表.append(4) # 直接修改原列表
print(f"函数内修改后,列表的ID:{id(列表)}")
return 列表
原始列表 = [1, 2, 3]
print(f"函数调用前,原始列表的ID:{id(原始列表)}")
结果 = 修改列表(原始列表)
print(f"函数调用后,原始列表:{原始列表}")
print(f"函数调用后,结果:{结果}")
print(f"函数调用后,原始列表的ID:{id(原始列表)}")
print(f"函数调用后,结果的ID:{id(结果)}")
输出结果类似:
代码语言:javascript代码运行次数:0运行复制函数调用前,原始列表的ID:140704726789632
函数内修改前,列表的ID:140704726789632
函数内修改后,列表的ID:140704726789632
函数调用后,原始列表:[1, 2, 3, 4]
函数调用后,结果:[1, 2, 3, 4]
函数调用后,原始列表的ID:140704726789632
函数调用后,结果的ID:140704726789632
解释:
- 原始列表和函数参数指向同一个对象(ID相同)
- 当我们在函数内执行
列表.append(4)
时,直接修改了原始列表对象 - 函数返回的仍然是原始列表对象
- 原始列表被修改
字典示例
代码语言:javascript代码运行次数:0运行复制def 修改字典(字典):
字典["年龄"] = 25 # 直接修改原字典
return 字典
原始字典 = {"姓名": "小明", "年龄": 18}
结果 = 修改字典(原始字典)
print(f"函数调用后,原始字典:{原始字典}")
print(f"函数调用后,结果:{结果}")
六、混合情况:可变对象中的不可变对象
代码语言:javascript代码运行次数:0运行复制def 修改嵌套结构(数据):
# 修改列表中的第一个元素(不可变对象)
数据[0] = 100 # 这会修改原列表
# 尝试修改元组中的元素
try:
数据[1][0] = 200 # 这会失败,因为元组是不可变的
except TypeError as e:
print(f"错误:{e}")
# 修改列表中的字典(可变对象)
数据[2]["分数"] = 95 # 这会修改原字典
return 数据
原始数据 = [1, (2, 3), {"姓名": "小明", "分数": 85}]
结果 = 修改嵌套结构(原始数据)
print(f"函数调用后,原始数据:{原始数据}")
print(f"函数调用后,结果:{结果}")
七、避免可变对象作为默认参数
Python中一个常见的陷阱是使用可变对象作为函数的默认参数:
代码语言:javascript代码运行次数:0运行复制# 错误示例
def 添加学生(姓名, 班级=[]):
班级.append(姓名)
return 班级
班级1 = 添加学生("小明")
print(班级1) # 输出:["小明"]
班级2 = 添加学生("小红")
print(班级2) # 输出:["小明", "小红"] - 可能不是预期结果!
解释:默认参数在函数定义时创建,而不是函数调用时。对于可变对象,这意味着所有函数调用都共享同一个默认对象。
正确做法:
代码语言:javascript代码运行次数:0运行复制# 正确示例
def 添加学生(姓名, 班级=None):
if 班级 is None:
班级 = []
班级.append(姓名)
return 班级
班级1 = 添加学生("小明")
print(班级1) # 输出:["小明"]
班级2 = 添加学生("小红")
print(班级2) # 输出:["小红"]
八、实际应用示例
1. 函数返回多个值
代码语言:javascript代码运行次数:0运行复制def 计算统计值(数字列表):
"""计算列表的最小值、最大值和平均值"""
最小值 = min(数字列表)
最大值 = max(数字列表)
平均值 = sum(数字列表) / len(数字列表)
return 最小值, 最大值, 平均值
数据 = [4, 7, 2, 9, 5]
最小, 最大, 平均 = 计算统计值(数据)
print(f"最小值:{最小},最大值:{最大},平均值:{平均}")
2. 函数作为参数传递
代码语言:javascript代码运行次数:0运行复制def 应用操作(函数, 数据):
"""将函数应用于数据"""
return 函数(数据)
def 平方(x):
return x ** 2
def 立方(x):
return x ** 3
数字 = 5
print(f"{数字}的平方是:{应用操作(平方, 数字)}")
print(f"{数字}的立方是:{应用操作(立方, 数字)}")
九、总结
- Python的参数传递机制是对象引用传递:传递的是对象的引用,而不是对象的副本或直接引用。
- 不可变对象(如整数、字符串、元组):
- 在函数内修改参数值会创建新对象
- 原始对象保持不变
- 行为类似于值传递
- 可变对象(如列表、字典、集合):
- 在函数内修改参数会直接影响原始对象
- 原始对象会被修改
- 行为类似于引用传递
- 避免使用可变对象作为默认参数,这可能导致意外行为。
- 理解对象的可变性对于编写正确的Python代码至关重要。