Python 中一切皆 对象
,而对象又分为 可变类型
和 不可变类型
。当将不同类型的对象作为参数传入函数时,往往会产生不同的效果。究其原因,是由于不同类型的对象有着各自不同的特性所导致的。
如果你有其他编程语言的基础,比如 C
、C++
、JavaScript
等,那么你也许听说过两种常见的函数传参方式:值传递
(pass by value)、引用传递
(pass by reference)。
Python 中函数参数的传递方式与大多数编程语言并不相同,既不是 值传递
,也不是 引用传递
。至于到底使用何种方式进行参数传递,等你阅读完本文就会明白了。不过,在此之前,我们先通过 JavaScript
代码来展示下到底什么是 值传递
,什么是 引用传递
,这样有助于我们理解 Python 中函数参数的传递方式。
值传递
在 JavaScript
中,如果函数接收到的参数是原始值类型(数值、字符串、布尔值等),函数参数的传递方式即为 值传递
。值传递
所表现出来的结果就是:在函数体内部修改传递进来的参数的值,并不会影响函数体外部原对象的值。
1 | var a = 1; |
以上代码中,在函数体内部对变量 p
的值进行了修改,但是并没有影响函数外部原有变量 a
的值。由于是 值传递
,所以 p
的值是对原有变量 a
的值的一份拷贝,修改 p
也就不会对 a
产生任何影响。
引用传递
如果函数接收到的参数是引用类型(数组、对象等),函数参数的传递方式即为 引用传递
。而 引用传递
所表现出来的结果是:在函数体内部修改传递进来的参数的值,将会直接改变函数体外部原对象的值。
1 | var arr = [1, 2, 3]; |
在这段代码中,同样在函数体内部对变量 p
的值进行了修改,函数外部原有变量 arr
的值跟着一起被改变了。这是因为 p
的值是对原有变量 arr
的值的引用,它们俩实际上指向同一个对象 [1, 2, 3]
。只要修改一个变量的值,另一个变量的值就会跟着改变。
值得注意的是,如果将上面的代码稍作修改,改为如下的代码。其结果表现出来的又像是 值传递
的特性。
1 | var arr = [1, 2, 3]; |
实际上这个示例函数参数的传递方式依然是 引用传递
。出现以上结果的原因是,在函数体内部,传递进来的 arr
的值实际上被整体替换成了另一个数组 [1, 2, 3, 4]
,现在 p
所指向的对象是新的数组 [1, 2, 3, 4]
,而不是函数体外部 arr
所指向的数组 [1, 2, 3]
了。
Python 中变量与赋值
搞明白了什么是 值传递
,什么是 引用传递
,我们回到 Python 语言中来。在正式介绍 Python 中函数参数的传递方式之前,让我们先来回顾下什么是 Python 中的 变量
,及 赋值
操作。
先来看一小段示例代码。
1 | 1 a = |
在 Python 中,a = 1
这句代码实际上执行效果是先在内存中创建了一个 int
类型的对象 1
,然后将对象 1
赋值给变量 a
。变量 a
就是对象 1
的一个标识,我们可以把它比作一个 标签
。当执行 b = a
的时候,可以理解为给对象 1
又贴了一个新的标签 b
。当代码执行到 a = a + 2
时,由于 int
类型对象是不可变的,即不能在原对象 1
上做原地加 2
的操作,所以相当于将对象 1
上的标签 a
撕了下来,贴在了一个新的对象 3
上,而对象 1
上原来贴的标签 b
依然在那里。
可以通过流程图来看下整个代码执行过程。
以上就是 Python 中创建 不可变类型
对象,并将其 赋值
给 变量
的过程。而对于 可变类型
对象的操作会产生不同的结果,下面通过对一个 list
对象操作进行演示。
1 | a = [] |
前面两行代码 a = []
和 b = a
与之前 不可变类型
对象的例子一样,只不过现在 a
、b
两个标签都贴在了可变对象 []
上。当执行 a.append(1)
的时候,由于 list
是 可变类型
, 所以无需再次创建一个新的列表对象,而是直接在原对象上面做更改操作,因此标签 a
仍然贴在这个对象上,a
和 b
最终依然指向的是同一个 list
对象,并且这个对象由原来的 []
变成了 [1]
。
我们还是通过流程图来看下整个代码执行过程。
由以上两个示例可以看出,实际上在 Python 中 变量
本身并没有所谓的 类型
,我们可以把它当作 标签
来理解,而 变量
所指向的 对象
才有 类型
的区分。对于 不可变类型
来说,改变 变量
的值,实际上是在对 变量
作重新赋值的操作;而对于 可变类型
,改变 变量
的值,实际上是直接修改它所指向的 对象
的值。
Python 中函数参数的传递方式
经过了前面的铺垫,现在可以正式介绍 Python 中函数参数的传递方式了。其实 Python 中唯一支持的函数参数的传递方式叫 对象的引用传递
(call by object reference)。函数的 形参
将获得传递进来的 实参
的引用副本。
结合 Python 中变量与赋值就很容易理解这种传参方式了。Python 中一切皆 对象
,而 变量
与 对象
的关系是,变量
会通过指向一个 对象
来引用这个 对象
的值,所以函数传参时,并不需要根据数据类型来决定使用 值传递
还是 引用传递
,只需要将 实参变量
的引用拷贝一份给 形参变量
就可以了,即让 形参
与 实参
指向同一个对象。
下面分别来看两个示例,第一个示例传递给函数 不可变类型
参数,第二个示例传递给函数 可变类型
参数。
1 | a = 1 |
1 | li = [1, 2, 3] |
两个示例的打印结果也证实了上面我们对 Python 中函数参数传递方式的分析。实际上,拿这两段示例代码跟最开始介绍 值传递
和 引用传递
时所写的 JavaScript
代码进行比较,你会发现其实它们得到的竟然是同一种结果。
无论在 Python
中,还是在 JavaScript
中,对于 可变类型
和 不可变类型
当作参数传入函数时,执行完函数体内部的代码后,最终体现出来的结果是相同的。但你要明白,这两种语言对于函数参数传递方式的处理机制是完全不同的,只不过它们最终体现出来的结果相同。