关于Python中函数传递参数为可变对象问题
python函数传值机制:
python中,函数传递参数实际上就是给变量产生一个复制品,然后将复制品传入到函数中,而变量本身不收影响。
"""
实现a、b变量的值交换,验证参数变化
"""
def data(a, b):
a, b = b, a
print("函数里a的值是:%d" % a)
print("函数里b的值是:%d" % b)
a = 2
b = 3
data(a, b)
print("交换结束后,变量a的值是%d" % a)
print("交换结束后,变量a的值是%d" % b)
运行结果:
函数里a的值是:3
函数里b的值是:2
交换结束后,变量a的值是2
交换结束后,变量a的值是3
从上边结果可以看出,函数内部的参数a,b交换了值,但是参数本身的值并没有改变,
当在主程序中调用 data() 函数时,系统分别为主程序和 data() 函数分配两块内存,用于保存它们的局部变量。将主程序中的 a、b 变量作为参数值传入 data() 函数,实际上是在 data() 函数内存中重新产生了两个变量 a、b,并将主程序内存中 a、b 变量的值分别赋值给 data() 函数内存中的 a、b 参数(就是对 data() 函数的 a、b 两个变量进行初始化)。此时,系统存在两个 a 变量、两个 b 变量,只是存在于不同的内存地址中而己。
这就是值传递的实质:当系统开始执行函数时,系统对形参执行初始化,就是把实参变量的值赋给函数的形参变量,在函数中操作的并不是实际的实参变量。
Python函数参数的引用传递:
如果实际参数的数据类型是可变对象(列表、字典),则函数参数的传递方式将采用引用传递方式。需要注意的是,引用传递方式的底层实现,采用的依然还是值传递的方式。
代码验证函数传可变对象:
"""
实现a、b变量的值交换,验证参数变化
"""
def data(d):
d[0], d[1] = d[1], d[0]
print("函数里d[0]的值是:%d" % d[0])
print("函数里d[1]的值是:%d" % d[1])
d=[1,2]
data(d)
print("交换结束后,变量d[0]的值是%d" % d[0])
print("交换结束后,变量d[1]的值是%d" % d[1])
返回结果:
函数里d[0]的值是:2
函数里d[1]的值是:1
交换结束后,变量d[0]的值是2
交换结束后,变量d[1]的值是1
从上面的运行结果来看,在 data() 函数里,列表两个元素的值被交换成功。不仅如此,主程序中列表两个元素的值也被交换了。这很容易造成一种错觉,即在调用函数时,传入函数的就是 d列表本身,而不是它的复制品。但这只是一种错觉,下面还是结合示意图来说明程序的执行过程。
程序开始创建了一个列表对象,并定义了一个 d引用变量(其实就是一个指针)指向列表对象,这意味着此时内存中有两个东西:对象本身和指向该对象的引用变量。此时在系统内存中的存储示意图如图所示:
主程序创建了列表对象后存储示意图
接下来主程序开始调用 data() 函数,在调用函数时,d 变量作为参数传入data() 函数,这里依然采用值传递方式:把主程序中 d 变量的值赋给函数的 d形参,从而完成函数的 d参数的初始化。其实,主程序中的 d是一个引用变量(也就是一个指针),它保存了列表对象的地址值,当把 d的值赋给函数的 d参数后,就是让函数的 d参数也保存这个地址值,即也会引用到同一个列表对象。显示了 d列表传入函数后的存储示意图。
从图来看,这种参数传递方式是不折不扣的值传递方式,系统一样复制了d 的副本传入函数。但由于 d 只是一个引用变量,因此系统复制的是 d 变量,并未复制列表本身。
当程序在函数中操作 d 参数时,由于 d只是一个引用变量,故实际操作的还是列表对象。此时,不管是操作主程序中的 d变量,还是操作函数里的 d参数,其实操作的都是它们共同引用的列表对象,它们引用的是同一个列表对象。因此,当在函数中交换 d 参数所引用列表对象的 a、b 两个元素的值后,可以看到在主程序中 d 变量所引用列表对象的 a、b 两个元素的值也被交换了。
为了更好地证明主程序中的 d 和函数中的 d 是两个变量,在函数的最后一行增加如下代码:
d = None
运行上面代码,结果是函数中的 d 变量不再指向任何对象,程序其他地方没有任何改变。主程序调用函数后,再次访问 d 变量的 a、b 两个元素,依然可以输出 2、1。可见,主程序中的 d 变量没有受到任何影响。实际上,当在函数中增加“d =None”代码后,在内存中的存储示意图如图所示。
图 将 data() 函数中的 d 赋值为 None 后存储示意图
从图来看,把函数中的 d 赋值为 None 后,在函数中失去了对列表对象的引用,不可再访问该列表对象。但主程序中的 d 变量不受任何影响,依然可以引用该列表对象,所以依然可以输出列表对象的 a、b 元素的值。
通过上面介绍可以得出如下两个结论:
- 不管什么类型的参数,在 Python 函数中对参数直接使用“=”符号赋值是没用的,直接使用“=”符号赋值并不能改变参数。
- 如果需要让函数修改某些数据,则可以通过把这些数据包装成字典、列表等可变对象,然后把字典、列表等可变对象作为参数传入函数,在函数中通过字典、列表的方法修改它们,这样才能改变这些数据。
我们在给函数传递可变对象时可能会遇到这个问题:
def linux_monitor(pid=0,pidlist = []):
pidlist.append(pid)
print(pid)
print(pidlist)
linux_monitor()
linux_monitor()
结果如下:
第二次调用的时候,pidlist并不是空的。
所以对于可变参数,特别需要注意,如列表等都可能产生这样的问题。
我们可以对代码做如下处理即可正常使用:
def linux_monitor(pid=0,pidlist = None):
if pidlist == None:
pidlist = []
pidlist.append(pid)
print(pid)
print(pidlist)