如果你曾经在Python函数内部修改过某个变量,然后对它在函数外部发生的变化感到困惑或惊讶,那么你并不是个例。这个问题也曾让我困扰了很长时间。
由于之前学习的教程中提到了“按值传递”和“按引用传递”,我原本以为Python肯定遵循这两种方式中的一种。但实际上并非如此。Python采用了一种略有不同的机制,一旦你理解了这一点,很多之前令人困惑的现象就会变得清晰起来。
在这篇文章中,你将了解到:
-
“按值传递”和“按引用传递”的含义
-
像C这样的其他语言是如何处理这个问题的
-
Python实际上采用的是哪种机制(通过对象引用进行传递)
-
可变类型和不可变类型如何影响函数内部的行为
目录
按值传递与按引用传递的解释
在讨论Python之前,我们先来快速定义这两个概念。
按值传递意味着将变量的副本传递给函数。在函数内部对这份副本进行的任何操作都不会影响到原始变量。
按引用传递意味着将变量实际存储的内存地址传递给函数。因此,在函数内部对变量进行的修改会直接影响到原始变量。
许多语言都支持这两种机制中的一种或两种。然而Python并不采用这两种方式中的任何一种——至少不是以传统意义上的那种方式。
C语言中的实现方式及示例
C语言就是一个明确支持这两种机制的语言例子。
以下是C语言中按值传递的示例。原始变量不会受到影响:
#include
void modify(int *n) {
*n = *n + 10;
printf("函数内部: %d\n", *n); }
int main() {
int x = 5;
modify(&x);
printf("函数外部: %d\n", x);
return 0; }
输出结果:
函数内部: 15
函数外部: 15 ← 原始变量发生了变化!
在C语言中,你需要明确指定是传递变量的指针还是它的值。而Python并没有给你这种选择权,但它所采用的机制实际上是非常合理的。
Python实际上采用的是哪种机制
Python采用了一种称为通过对象引用进行传递的机制(有时也被称为“按赋值方式传递”)。
在Python中,当你将一个变量传递给一个函数时,你实际上传递的是该变量所指向的对象的引用,而不是该值的副本,更不是变量本身。
接下来会发生什么,完全取决于该对象是可变的(可以在原位置上进行修改)还是不可变的(不能在原位置上进行修改)。
可变类型与不可变类型
在Python中,不可变类型包括int、float、str和tuple。这些对象不能在原位置上进行修改。当你在函数内部“修改”其中一个对象时,Python会创建一个全新的对象,而原来的对象则保持不变。
def modify_number(n):
n = n + 10
print("函数内部:", n)
x = 5
modify_number(x)
print("函数外部:", x)
输出结果:
函数内部: 15
函数外部: 15 ← 原始值未发生变化
可变类型包括list、dict和set。这些类型可以在原位置上进行修改。当你在函数内部修改其中一个对象时,你实际上是在修改调用者所持有的那个对象的副本。
def modify_list(items):
items.append(99)
print("函数内部:", items)
my_list = [1, 2, 3]
modify_list(my_list)
print("函数外部:", my_list)
输出结果:
函数内部: [1, 2, 3, 99]
函数外部: [1, 2, 3, 99] ← 原始对象发生了变化!
关键在于:Python并不会根据你传递数据的方式来决定其行为,而是会根据你传递的对象类型来判断应该发生什么。
结论
Python并不采用“按值传递”或“按引用传递”的方式。它采用的是对象引用传递,即函数接收的是对对象的引用,而该对象是否可以在原位置上进行修改,才决定了后续会发生什么。
总结如下:
-
不可变类型(
int、str、tuple):在函数内部会创建一个新对象,原始对象保持不变 -
可变类型(
list、dict、set):原始对象会被直接修改
一旦理解了这一点,很多“为什么Python会这样处理”这样的疑问就会变得合情合理了。如果你刚开始学习Python中的函数,记住这个概念,它会帮你避免很多调试上的麻烦。