和其他语言一样,Python 也有自己的参数传递规则。了解 Python 中参数的传递机制,具有十分重要的意义,这往往能让我们写代码时少犯错误,提高效率。

参数传递

参数传递方式通常有两种:值传递和引用传递

值传递,通常是指拷贝参数的值,然后传递给函数里的新变量。这样原变量和新变量之间互相独立,互不影响。Java 就是典型的值传递语言,在 Java 中,参数使用值传递方式,传递的值是引用。在 Go 语言中只存在值传递(要么是该值的副本,要么是指针的副本),不存在引用传递。之所以对于引用类型的传递可以修改原内容数据,是因为在底层默认使用该引用类型的指针进行传递,但仍是使用指针的副本,依旧是值传递。

引用传递,通常是指把参数的引用传给新的变量,这样原变量和新变量就会指向同一块内存地址。如果改变了其中任何一个变量的值,那么另外一个变量也会相应地随之改变。C/C++ 中的指针,在使用时可以通过传递变量的指针,在函数中完成对变量的修改和值的修改。因为引用传递使得形参和实参一致,对参数的任何改动,也将同步改变对应的值。

变量及其赋值

不可变变量赋值

a = 1
b = a
a = a + 1

这个代码段中,前两行运行后,实际的赋值关系如下所示。

传递引用参数_值传递和引用传递_引用的传递

最后执行 a = a + 1。需要注意的是,Python 的基础数据类型,例如整型(int)、字符串(string)等,是不可变的。所以 a = a + 1,并不是让 a 的值增加 1,而是表示重新创建了一个新的值为 2 的对象,并让 a 指向它。但是 b 仍然不变,仍然指向 1 这个对象。

引用的传递_传递引用参数_值传递和引用传递

这里的 a 和 b,开始只是两个指向同一个对象的变量而已。简单的赋值 b = a,并不表示重新创建了新对象,只是让同一个对象被多个变量指向或引用。同时,指向同一个对象,也并不意味着两个变量就被绑定到了一起。如果给其中一个变量重新赋值,并不会影响其他变量的值。

可变变量的赋值

l1 = [123]
l2 = l1
l1.append(4)
print(l1)  # [1, 2, 3, 4]
print(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 中的变量可以手动删除,但是对象不能手动删除。

l = [123]
del l
# l 变量被删除之后,以后就无法访问 l,但是对象 [1, 2, 3] 仍然存在

Python 程序运行时,其自带的垃圾回收系统会跟踪每个对象的引用。如果这个对象除了被已删除的变量引用,没有在其他地方被引用,那么就会被回收。

在 Python 中:

Python 函数的参数传递

Remember that arguments are passed by assignment in Python. Since assignment just creates references to objects, there’s no alias between an argument name in the caller and callee, and so no call-by-reference per Se.

准确地说,Python 的参数传递是赋值传递 (pass by assignment),或者叫作对象的引用传递(pass by object reference)。Python 所有的数据类型都是对象,所以参数传递时,只是让新变量与原变量指向相同的对象而已,并不存在值传递或是引用传递一说。

def my_func1(b):
    b = 2


a = 1
my_func1(a)
print(a)  # 1

这里的参数传递,使变量 a 和 b 同时指向了 1 这个对象。但当我们执行到 b = 2 时,系统会重新创建一个值为 2 的新对象,并让 b 指向它;而 a 仍然指向 1 这个对象。所以 a 的值不变,仍然为 1。

def my_func2(b):
    b = 2
    return b


a = 1
a = my_func2(a)
print(a)  # 2

可以稍作改变,让函数返回新变量,并赋值给原变量 a。这样 a 就指向了一个新的值为 2 的对象,a 的值也因此发生变化。

def my_func3(l2):
    l2.append(4)


l1 = [123]
my_func3(l1)
print(l1)  # [1, 2, 3, 4]

当可变对象当作参数传入函数里的时候,改变可变对象的值,就会影响所有指向它的变量。这里 l1 和 l2 先是同时指向值为 [1, 2, 3] 的列表。由于列表可变,执行 append() 函数对其末尾加入新元素 4 时,变量 l1 和 l2 的值也都随之改变了。

def my_func4(l2):
    l2 = l2 + [4]


l1 = [123]
my_func4(l1)
print(l1)  # [1, 2, 3]

这里 l2 = l2 + [4],表示创建了一个末尾加入元素 4 的新列表,并让 l2 指向这个新的对象。这个过程与 l1 无关,因此 l1 的值不变。

def my_func5(l2):
    l2 = l2 + [4]
    return l2


l1 = [123]
l1 = my_func5(l1)
print(l1)  # [1, 2, 3, 4]

要改变 l1 的值,仍然可以让函数返回新的对象,并让原变量指向新返回的变量,这样就可以改变原变量的值了。这一点对于不可变对象来说,同样适用。

改变变量和重新赋值的区别:

可变对象与不可变对象,在函数处理中是否会改变原变量值,取决于函数中的处理。如果需要改变变量,推荐使用将函数返回值赋值给原变量的方式,这样更简单清晰。

总结

和其他语言不同的是,Python 中参数的传递既不是值传递,也不是引用传递,而是赋值传递,或者是叫对象的引用传递。这里的赋值或对象的引用传递,不是指向一个具体的内存地址,而是指向一个具体的对象。

如果想通过一个函数来改变某个变量的值,通常有两种方法:一种是直接将可变数据类型(比如列表,字典,集合)当作参数传入,直接在其上修改;第二种则是创建一个新变量,来保存修改后的值,然后将其返回给原变量。在实际工作中更倾向于使用后者,因为其表达清晰明了,不易出错。

限 时 特 惠: 本站每日持续更新海量各大内部创业教程,一年会员只需98元,全站资源免费下载 点击查看详情
站 长 微 信: lzxmw777

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注