Python中的值传递,引用传递or其他? |
文章目录
- 一. 什么是值传递和引用传递
- 二. Python 变量及其赋值
- 2.1. 例子解释
- 2.2. 总结
- 三. Python函数的参数传递
- 3.1. 例子解释
- 3.2. 总结
- 3.3. 函数中参数前带*
一. 什么是值传递和引用传递
- 如果你接触过其他的编程语言,比如 C/C++,常见的参数传递有 2 种:值传递和引用传递。所谓值传递,通常就是拷贝参数的值,然后传递给函数里的新变量。这样,原变量和新变量之间互相独立,互不影响。
- 以c++代码为例,下面的 swap() 函数,把 a 和 b 的值拷贝给了 x 和 y,然后再交换 x 和 y 的值。这样一来,x 和 y 的值发生了改变,但是 a 和 b 不受其影响,所以值不变。这种方式,就是我们所说的值传递。
#include <iostream>
using namespace std;
// 交换2个变量的值
void swap(int x, int y) {
int temp;
temp = x; // 交换x和y的值
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;
}
Before swap, value of a :1
Before swap, value of b :2
After swap, value of a :1
After swap, value of b :2
- 所谓引用传递,通常是指把参数的引用传给新的变量,这样,原变量和新变量就会指向同一块内存地址。如果改变了其中任何一个变量的值,那么另外一个变量也会相应地随之改变。
- C++ 代码为例,上述例子中的 swap() 函数,如果改成下面的形式,声明引用类型的参数变量:原变量 a 和 b 的值被交换了,因为引用传递使得 a 和 x,b 和 y 一模一样,对 x 和 y 的任何改变必然导致了 a 和 b 的相应改变。
void swap(int& x, int& y) {
int temp;
temp = x; // 交换x和y的值
x = y;
y = temp;
return;
}
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 变量及其赋值
2.1. 例子解释
- Python 变量及其赋值
a = 1
b = a
a = a + 1
- 这里首先将 1 赋值于 a,即 a 指向了 1 这个对象,如下面的流程图所示:
- 接着 b = a 则表示,让变量 b 也同时指向 1 这个对象。这里要注意,Python 里的对象可以被多个变量所指向或引用。
- 最后执行 a = a + 1。需要注意的是,Python 的数据类型,例如整型(int)、字符串(string)等等,是不可变的。所以,a = a + 1,并不是让 a 的值增加 1,而是表示重新创建了一个新的值为 2 的对象,并让 a 指向它。但是 b 仍然不变,仍然指向 1 这个对象。
- 通过这个例子你可以看到,这里的 a 和 b,开始只是两个指向同一个对象的变量而已,或者你也可以把它们想象成同一个对象的两个名字。简单的赋值 b = a,并不表示重新创建了新对象,只是让同一个对象被多个变量指向或引用。同时,指向同一个对象,也并不意味着两个变量就被绑定到了一起。如果你给其中一个变量重新赋值,并不会影响其他变量的值。
l1 = [1, 2, 3]
l2 = l1
l1.append(4)
l1
[1, 2, 3, 4]
l2
[1, 2, 3, 4]
- 同样的,我们首先让列表 l1 和 l2 同时指向了[1, 2, 3]这个对象。
- 由于列表是可变的,所以 l1.append(4) 不会创建新的列表,只是在原列表的末尾插入了元素 4,变成[1, 2, 3, 4]。由于 l1 和 l2 同时指向这个列表,所以列表的变化会同时反映在 l1 和 l2 这两个变量上,那么,l1 和 l2 的值就同时变为了[1, 2, 3, 4]。
- 另外,需要注意的是,Python 里的变量可以被删除,但是对象无法被删除。比如下面的代码:
- del l 删除了 l 这个变量,从此以后你无法访问 l,但是对象[1, 2, 3]仍然存在。Python 程序运行时,其自带的垃圾回收系统会跟踪每个对象的引用。如果[1, 2, 3]除了 l 外,还在其他地方被引用,那就不会被回收,反之则会被回收。
l = [1, 2, 3]
del l
2.2. 总结
- 变量的赋值,只是表示让变量指向了某个对象,并不表示拷贝对象给变量;而一个对象,可以被多个变量所指向。
- 可变对象(列表,字典,集合等等)的改变,会影响所有指向该对象的变量。
- 对于不可变对象(字符串、整型、元组等等),所有指向该对象的变量的值总是一样的,也不会改变。但是通过某些操作(+= 等等)更新不可变对象的值时,会返回一个新的对象。
- 变量可以被删除,但是对象无法被删除。
三. Python函数的参数传递
3.1. 例子解释
- 从上述 Python 变量的命名与赋值的原理讲解中,相信你能举一反三,大概猜出 Python 函数中参数是如何传递了吧?
- 准确地说,Python 的参数传递是赋值传递 (pass by assignment),或者叫作对象的引用传递(pass by object reference)。Python 里所有的数据类型都是对象,所以参数传递时,只是让新变量与原变量指向相同的对象而已,并不存在值传递或是引用传递一说。
- 比如,我们来看下面这个例子:
def my_func1(b):
b = 2
a = 1
my_func1(a)
a
1
- 上面的参数传递,使变量 a 和 b 同时指向了 1 这个对象。但当我们执行到 b = 2 时,系统会重新创建一个值为 2 的新对象,并让 b 指向它;而 a 仍然指向 1 这个对象。所以,a 的值不变,仍然为 1。
- 那么对于上述例子的情况,是不是就没有办法改变 a 的值了呢?答案当然是否定的,我们只需稍作改变,让函数返回新变量,赋给 a。这样,a 就指向了一个新的值为 2 的对象,a 的值也因此变为 2。
def my_func2(b):
b = 2
return b
a = 1
a = my_func2(a)
a
2
- 不过,当可变对象当作参数传入函数里的时候,改变可变对象的值,就会影响所有指向它的变量。比如下面的例子:
- 这里 l1 和 l2 先是同时指向值为[1, 2, 3]的列表。不过,由于列表可变,执行 append() 函数,对其末尾加入新元素 4 时,变量 l1 和 l2 的值也都随之改变了。
def my_func3(l2):
l2.append(4)
l1 = [1, 2, 3]
my_func3(l1)
l1
[1, 2, 3, 4]
- 但是,下面这个例子,看似都是给列表增加了一个新元素,却得到了明显不同的结果。
- 为什么 l1 仍然是[1, 2, 3],而不是[1, 2, 3, 4]呢?要注意,这里 l2 = l2 + [4],表示创建了一个“末尾加入元素 4“的新列表,并让 l2 指向这个新的对象。这个过程与 l1 无关,因此 l1 的值不变。 当然,同样的,如果要改变 l1 的值,我们就得让上述函数返回一个新列表,再赋予 l1 即可。
def my_func4(l2):
l2 = l2 + [4]
l1 = [1, 2, 3]
my_func4(l1)
l1
[1, 2, 3]
# 返回
def my_func5(l2):
l2 = l2 + [4]
return l2
l1 = [1, 2, 3]
l1 = my_func5(l1)
l1
[1, 2, 3, 4]
3.2. 总结
- 和其他语言不同的是,Python 中参数的传递既不是值传递,也不是引用传递,而是赋值传递,或者是叫对象的引用传递。
- 需要注意的是,这里的赋值或对象的引用传递,不是指向一个具体的内存地址,而是指向一个具体的对象。
- 如果对象是可变的,当其改变时,所有指向这个对象的变量都会改变。
- 如果对象不可变,简单的赋值只能改变其中一个变量的值,其余变量则不受影响。
3.3. 函数中参数前带*
- 查阅资料后发现,参数前面加上
*
号 ,意味着参数的个数不止一个,另外带一个星号(*)
参数的函数传入的参数存储为一个元组(tuple),带两个(*)
号则是表示字典(dict) - 下面我们实际操作一下:
def t1(param1, *param2):
print(param1)
print(param2)
t1(1, 2, 3, 4)
# 1
# (2, 3, 4)
def t2(param1, **param2):
print(param1)
print(param2)
t2(1, a=1, b=2, c=3)
# 1
# {'a': 1, 'b': 2, 'c': 3}
- 此外,一个
(*)
号还可以解压参数列表:
def t3(p1, p2):
print(p1, p2)
args = [1, 2]
t3(*args)
# 1 2
- 最后,还可以同时使用一个
(*)
和(**)
def t4(a, b=10, *args, **kwargs):
print(a)
print(b)
print(args)
print(kwargs)
t4(1, 2, 3, 4, e=5, f=6, g=7)
# 1
# 2
# 3 4
# {'e': 5, 'g': 7, 'f': 6}