这是一个非常核心且重要的 Python 概念。理解参数和返回值的传递机制,是从“会写代码”到“写好代码”的关键一步。
我们可以用一个简单的比喻来理解:函数就像一个功能专一的加工机器。
参数 (Argument):是你投入机器的原材料。函数体 (Function Body):是机器内部的加工过程。返回值 (Return Value):是机器加工完成后吐出的成品。
现在,我们来深入探讨这个“投入”和“产出”的过程是如何在 Python 中实现的。
参数 (Argument):数据如何进入函数
参数是将数据从外部传递到函数内部的通道。当你调用一个函数时,你提供的值就是参数。
核心传递机制:赋值传递 (Pass by Assignment)
Python 的参数传递方式既不同于 C++ 的“值传递”,也不同于其“引用传递”。最准确的描述是赋值传递 (Pass by Assignment) 或 对象引用传递 (Pass by Object Reference)。
理解这个机制的关键在于记住 Python 的一句话:“万物皆对象”。变量名本身不是数据的容器,而是一个指向数据对象的标签。
当你把一个变量作为参数传递给函数时,实际发生的是:
在函数内部,创建了一个新的标签(函数内的局部变量),让它指向外部标签所指向的同一个对象。
这个机制对不同类型的对象会产生看起来不同的效果:
1. 当传递不可变对象 (Immutable Objects) 时
像数字 (int, float)、字符串 (str)、元组 (tuple) 这类对象,其自身的值是不能被改变的。
def try_to_change_number(num):
print(f"函数开始时,num 的 ID: {id(num)}") # 查看对象内存地址
num = num + 10 # 这里的关键!
print(f"函数结束时,num 的 ID: {id(num)}")
x = 5
print(f"调用前,x 的 ID: {id(x)}")
try_to_change_number(x)
print(f"调用后,x 的值: {x}") # x 的值并没有改变
# 输出:
# 调用前,x 的 ID: 4488582064
# 函数开始时,num 的 ID: 4488582064 <-- x 和 num 指向同一个对象 5
# 函数结束时,num 的 ID: 4488582384 <-- num 指向了一个新的对象 15
# 调用后,x 的值: 5
发生了什么?
调用函数时,函数内的标签 num 指向了和外部标签 x 相同的对象 5。当执行 num = num + 10 时,因为数字 5 是不可变的,Python 无法在原地修改它。所以 Python 创建了一个新的数字对象 15。然后,函数内的标签 num 被重新赋值,转而指向了这个新的对象 15。整个过程,外部的标签 x 始终指向原来的对象 5,从未受影响。
所以,对于不可变对象,其效果看起来非常像“值传递”。
2. 当传递可变对象 (Mutable Objects) 时
像列表 (list)、字典 (dict)、集合 (set) 这类对象,其自身的内容是可以被修改的。
def change_list(my_list):
print(f"函数开始时,my_list 的 ID: {id(my_list)}")
my_list.append(4) # 这里的关键!
print(f"函数结束时,my_list 的 ID: {id(my_list)}")
items = [1, 2, 3]
print(f"调用前,items 的 ID: {id(items)}")
change_list(items)
print(f"调用后,items 的值: {items}") # items 的值被改变了!
# 输出:
# 调用前,items 的 ID: 4545224768
# 函数开始时,my_list 的 ID: 4545224768 <-- items 和 my_list 指向同一个列表对象
# 函数结束时,my_list 的 ID: 4545224768 <-- my_list 仍然指向同一个对象
# 调用后,items 的值: [1, 2, 3, 4]
发生了什么?
调用函数时,函数内的标签 my_list 指向了和外部标签 items 相同的列表对象。当执行 my_list.append(4) 时,我们没有对 my_list 这个标签进行重新赋值。我们是通过这个标签,直接修改了它所指向的那个列表对象本身的内容。因为外部的 items 和内部的 my_list 指向的是同一个对象,所以当这个对象被修改后,通过 items 也能看到这个变化。
所以,对于可变对象,其效果看起来非常像“引用传递”。
返回值 (Return Value):数据如何从函数出来
返回值是将数据从函数内部传递回外部调用者的通道。它是一个函数执行完毕后留下的“成果”。
核心传递机制:return 关键字
return 关键字做了两件事:
立即终止函数的执行。return 后面的任何代码都不会被执行。将 return 后面跟着的值(或对象引用)发送回调用该函数的地方。
def add_and_multiply(a, b):
sum_val = a + b
product_val = a * b
# 将一个包含两个值的元组返回
return (sum_val, product_val)
# 调用函数,并将返回值赋给一个变量
result = add_and_multiply(3, 4)
print(f"函数返回的结果是: {result}")
print(f"结果的类型是: {type(result)}")
# 我们可以通过解包来分别获取这两个值
sum_result, product_result = result
print(f"和是: {sum_result}, 积是: {product_result}")
# 输出:
# 函数返回的结果是: (7, 12)
# 结果的类型是:
# 和是: 7, 积是: 12
发生了什么?
add_and_multiply(3, 4) 被调用。函数内部计算出 sum_val 为 7,product_val 为 12。return (sum_val, product_val) 创建了一个元组对象 (7, 12),并将其引用返回。在外部,result = ... 这条语句将 result 这个标签指向了函数返回的那个元组对象。
如果没有 return 语句会怎样?
所有函数都有返回值。如果一个函数没有显式的 return 语句,它会自动在末尾执行 return None。
def just_print(name):
print(f"你好, {name}")
output = just_print("张三")
print(f"函数的返回值是: {output}")
print(f"返回值的类型是: {type(output)}")
# 输出:
# 你好, 张三
# 函数的返回值是: None
# 返回值的类型是:
总结
参数传递 (数据输入):通过赋值传递实现。函数内部会创建一个新变量名(标签),指向外部变量名所指向的同一个对象。
如果对象是不可变的(如数字、字符串),在函数内对其重新赋值,只会让内部变量指向一个新对象,不影响外部。如果对象是可变的(如列表、字典),在函数内通过方法修改对象内容,这个修改会反映到外部。
返回值传递 (数据输出):通过 return 关键字实现。它将一个值或对象的引用从函数内部“弹出”到外部,外部代码可以用一个变量来“接住”这个结果。如果函数没有 return,则默认返回 None。
理解这两个机制,可以帮助我们精确地控制数据在程序不同部分之间的流动,编写出更加清晰的代码。