如果你曾经在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中,不可变类型包括intfloatstrtuple。这些对象不能在原位置上进行修改。当你在函数内部“修改”其中一个对象时,Python会创建一个全新的对象,而原来的对象则保持不变。

def modify_number(n):
     n = n + 10
     print("函数内部:", n)

x = 5

modify_number(x)

print("函数外部:", x)

输出结果:

函数内部: 15

函数外部: 15 ← 原始值未发生变化

可变类型包括listdictset。这些类型可以在原位置上进行修改。当你在函数内部修改其中一个对象时,你实际上是在修改调用者所持有的那个对象的副本。

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并不采用“按值传递”或“按引用传递”的方式。它采用的是对象引用传递,即函数接收的是对对象的引用,而该对象是否可以在原位置上进行修改,才决定了后续会发生什么。

总结如下:

  • 不可变类型intstrtuple):在函数内部会创建一个新对象,原始对象保持不变

  • 可变类型listdictset):原始对象会被直接修改

一旦理解了这一点,很多“为什么Python会这样处理”这样的疑问就会变得合情合理了。如果你刚开始学习Python中的函数,记住这个概念,它会帮你避免很多调试上的麻烦。

Comments are closed.