阿里面试题:说一下Python里参数是如何传递的?
今天我们来聊聊 Python 里的参数传递问题。你可能已经知道,Python 函数的参数是怎么传递的,但要深入了解它的底层机制,才会让你在写代码时避免一些常见的坑。
你有没有遇到过这样的情况:你传递了一个列表到函数里,结果测试一看,发现这个列表变了样!本来你以为它不应该被修改,但它却莫名其妙地被改动了,这就让调试变得一团糟。搞了半天,才发现其实是传参的时候出现了问题。
所以,了解 Python 中是如何传递参数的,能帮我们少走很多弯路,尤其是在复杂的代码里。那我们今天就来揭开这个神秘的面纱,看看 Python 中的参数到底是怎么传递的。
要回答这个问题,咱们首先得搞清楚在编程语言中,值传递和引用传递是怎么回事。比如在 C++ 里,参数传递分为两种方式——值传递和引用传递。
值传递的意思就是把参数的值拷贝一份给函数中的变量,修改这个变量并不会影响外部的原变量。引用传递则是将变量的“引用”传给函数,原变量和新变量指向的是同一块内存地址,修改一个会影响到另一个。
假设我们在 C++ 中有下面的代码:
#include <iostream>
using namespace std;void swap(int x, int y) {
int temp;
temp = x;
x = y;
y = temp;
return;
}
int main () {
int a = 1;
int b = 2;
cout << "Before swap, value of a :" << a << endl;
cout << "Before swap, value of b :" << b << endl;
swap(a, b);
cout << "After swap, value of a :" << a << endl;
cout << "After swap, value of b :" << b << endl;
return 0;
}
在这段代码里,swap() 函数通过值传递将 a 和 b 的值拷贝到 x 和 y,然后交换 x 和 y 的值。然而,交换完毕后,a 和 b 仍然保持不变,因为 x 和 y 只是 a 和 b 的拷贝,二者互不干扰。
而如果我们改成引用传递,就像下面这样:
void swap(int& x, int& y) {
int temp;
temp = x;
x = y;
y = temp;
return;
}
这时,x 和 y 成了 a 和 b 的引用,修改 x 和 y 的值就等同于修改 a 和 b 的值,所以输出就会变成:
Before swap, value of a :1
Before swap, value of b :2
After swap, value of a :2
After swap, value of b :1
好了,我们先回到 Python,看看它是怎么做的。在 Python 中,参数传递并不简单地分为值传递和引用传递。实际上,Python 的参数传递是基于“对象引用”传递的,这就是所谓的 “传递对象的引用”,但并不是传统意义上的引用传递。
首先,我们需要理解 Python 中的变量和赋值机制。在 Python 中,变量其实并不直接保存数据,而是指向某个对象。举个简单的例子:
a = 1
b = a
a = a + 1
在这段代码中,a 最初指向值 1,然后 b 被赋值为 a,这时候 a 和 b 都指向同一个对象——即 1。
接下来,当我们做 a = a + 1 时,Python 并没有修改 a 指向的值,而是创建了一个新的对象 2,并让 a 指向它,而 b 依然指向原来的 1。所以最终,a 的值变成了 2,而 b 依然是 1。
所以,Python 中的赋值并不是简单的值拷贝,而是让变量指向一个对象。如果重新给一个变量赋值,它会指向一个新的对象,不会影响原有的对象。
这个机制对于不可变类型(比如 int、str、tuple)和可变类型(比如 list、dict)的处理方式有所不同。
我们再看一个涉及可变对象的例子:
l1 = [1, 2, 3]
l2 = l1
l1.append(4)
print(l1) # [1, 2, 3, 4]
print(l2) # [1, 2, 3, 4]
在这个例子中,l1 和 l2 都指向同一个列表对象。由于列表是可变的,调用 l1.append(4) 会修改这个列表对象本身,所以 l1 和 l2 的值都发生了变化。
总结一下,Python 中的参数传递其实是通过传递对象的引用来实现的,但这并不等同于传统意义上的引用传递。
对于不可变类型(如整数、字符串),因为每次赋值都会创建一个新的对象,所以你不会看到原对象被改变。而对于可变类型(如列表、字典),修改对象的内容会影响到所有指向该对象的变量。
如果你传递一个列表给一个函数,函数内部修改了这个列表,它的改变会反映到外部,除非你显式地创建了一个列表的副本。这是因为函数接收到的仍然是原对象的引用,而不是它的拷贝。
比如,下面的代码:
def modify_list(lst):
lst.append(4)l = [1, 2, 3]
modify_list(l)
print(l) # [1, 2, 3, 4]
这里传入的 l 作为参数传递给 modify_list 函数,函数内部的 lst.append(4) 会修改 l,因为 lst 和 l 指向同一个对象。
如果你不希望函数内部修改传入的列表,可以显式地传递该列表的副本:
def modify_list(lst):
lst.append(4)l = [1, 2, 3]
modify_list(l[:]) # 传递列表的副本
print(l) # [1, 2, 3]
这样就不会影响原来的 l 了,因为传递的是列表的副本,而不是原列表的引用。
总结来说,Python 中的参数传递是通过对象引用来进行的,具体表现为:
对于不可变类型,传递的是对象的引用,但由于每次赋值都会创建新对象,所以不会影响原对象; 对于可变类型,传递的是对象的引用,修改对象会影响到所有指向它的变量。
理解了这个原理后,你就能在编程过程中避免因为参数传递机制导致的问题了,调试时也会更有针对性。
对编程、职场感兴趣的同学,大家可以联系我微信:golang404,拉你进入“程序员交流群”。
虎哥作为一名老码农,整理了全网最全《python高级架构师资料合集》。