Python 列表、变量以及参数传递——从删除列表第一个元素说起

Python 的列表、变量以及参数传递一直都是一个挺让人头疼的问题。什么时候是值传递,什么时候是引用传递,总是傻傻搞不清。

举个例子来说,下面三段代码都希望能够删除列表第一个元素,但为什么前两个能实现功能,但第三个不行呢?

# Example 1
def delete_head(new_list):
    del new_list[0]

old_list = [0, 1, 2, 3, 4]

delete_head(old_list)
print(old_list)

# Output: [1, 2, 3, 4]
# Example 2
def tail(new_list):
    return new_list[1:]

old_list = [0, 1, 2, 3, 4]

old_list = tail(old_list)
print(old_list)

# Output: [1, 2, 3, 4]
# Example 3
def bad_delete_head(new_list):
    new_list = new_list[1:]

old_list = [0, 1, 2, 3, 4]

bad_delete_head(old_list)
print(old_list)

# Output: [0, 1, 2, 3, 4]

为了解决这个问题,我们需要对 Python 的变量有一个基本的认识:

  • Python 中的变量都是对内存中某个对象的引用。
    • 一般变量(不可更改对象)是对数据对象的引用,列表、字典(可更改对象)是对一般变量(不可更改对象)的引用。
  • Python 的函数参数传递总是“值传递”,或者说,传递的总是对内存中某个对象的引用关系。

基于这样的初步认识,我们再来依次分析一下上面的三段代码。

例子 1

# Example 1
def delete_head(new_list):
    del new_list[0]

old_list = [0, 1, 2, 3, 4]

delete_head(old_list)
print(old_list)

# Output: [1, 2, 3, 4]

当第 5 行 old_list = [0, 1, 2, 3, 4] 这段代码执行过后,内存中是这样的:



当我们调用 delete_head 函数的时候,Python 解释器将我们传入的参数 old_list 复制了一份成为 new_list,像这样:



这里我们再次强调,Python 始终是“值传递”;而这里的“值”,指的是引用关系。

接下来,程序执行到第 3 行 del new_list[0]del 表示从内存中删掉一个变量,同时删除链接到它的引用关系。我们将与 new_list[0] 指向的变量、相关的引用关系标红:



这里对于 Data 0,没有其他的变量引用到它,因此这个数据对象也会被销毁。

当完成删除后,内存中就是这样的:



很显然,这改变了 old_list 内容,符合我们的期望。

例子 2

# Example 2
def tail(new_list):
    return new_list[1:]

old_list = [0, 1, 2, 3, 4]

old_list = tail(old_list)
print(old_list)

# Output: [1, 2, 3, 4]

列表初始化、函数调用的传参和例子 1 相似,我们得到这样的内存结构:



现在,new_list[1:] 语句会复制一份新的列表,内存会变成这样:



之后,我们通过 return,将这个列表赋值给了 old_list,如图:



于是,这个程序成功地改变了 old_list 内容,符合我们的期望。

例子 3

# Example 3
def bad_delete_head(new_list):
    new_list = new_list[1:]

old_list = [0, 1, 2, 3, 4]

bad_delete_head(old_list)
print(old_list)

# Output: [0, 1, 2, 3, 4]

对于这个程序,直到 new_list[1:] 都与例子 2 一模一样。在 new_list[1:] 复制一份新的列表之后:



接下来,程序将复制后的列表赋值给了 new_list,然后函数结束,new_list 被释放:



在这个过程中,old_list 是始终没有被修改的,使得最后结果是错误的。

后记

今天一个同学在 QQ 上问我了这样的一个问题,于是我就跑去查了些材料、画了些图,然后给他作了解答。想着好不容易画的图不能这么轻易浪费,于是将回答整理了一下,形成了这篇文章。

当然,这篇文章只提到了列表的参数传递,并没有提到普通变量的传递,而且我也不确定内容是否完全正确……如果有什么错误或者不太恰当的地方,还请指出。

$\empty$

参考资料

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。
本文链接:https://blog.ceba.tech/2020/12/Summary-of-Python-List-and-Var/