Python技术迷

阿里面试题:说一下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高级架构师资料合集》。

资料包含了《IDEA视频教程》、《最全python面试题库》、《最全项目实战源码及视频》及《毕业设计系统源码》,总量高达650GB,全部免费领取。