引子

       在群里和人讨论了有关python参数传递的机制。对方说是赋值传递,我持的观点是引用传递,讨论了许久,对方仍没讲解明白赋值传递的实现。我查看官方文档,官方文档的描述为:

Remember that arguments are passed by assignment in Python.将官方文档切换为中文结果为:请记住在 Python 中参数是通过赋值来传递的。之前看官方文档也没去深究,经过今天的讨论,浅显地对探究了官方文档的这段话,进行了一些思考。

英文版文档链接:https://docs.python.org/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

中文版文档链接:https://docs.python.org/zh-cn/3/faq/programming.html#how-do-i-write-a-function-with-output-parameters-call-by-reference

值传递与引用传递


       值传递的方式,传入函数的参数是实际参数的复制品,不管在函数中对这个复制品如何操作,实际参数本身不会受到任何影响         引用传递是指在调用函数时将实际参数的地址传递到函数中,那么在函数中所进行的修改,将影响实际参数。

python函数的参数的传递

       对于pythoner来说,在python中一切皆对象是众所周知的。对象又分为类似于数字不可变的对象和类似于字典可变的对象。

       先看下面的代码

def test(a , b) :    a, b = b, a    print("test函数里,a的值是", a, ";b的值是", b)    print("test函数里,a的内存地址是", id(a), ";b的内存地址是", id(b))a = 1b = 2test(a , b)print("交换结束后,a的值是", a , ";变量b的值是", b)print("交换结束后,a的内存地址是", id(a) , ";b的内存地址是", id(b))


          运行的结果如下

test函数里,a的值是 2 ;b的值是 1test函数里,a的内存地址是 140705270535872 ;b的内存地址是 140705270535840交换结束后,a的值是 1 ;变量b的值是 2交换结束后,a的内存地址是 140705270535840 ;b的内存地址是 140705270535872

       从运行结果来看,在test函数里,a和b的值分别是2和1,交换结束后,变量a和b的值依然是1和2,并没有发生改变,程序中定义的变量a和b并不是test函数里的a和b。

       可以推测,test函数里的a和b只是主程序中变量a和b的复制品。

       代码开始只是定义了a和b两个变量,当程序运行到test函数时,进入 test函数,并将主程序中的a、b作为变量传入到test函数,此时,系统分别为主程序和test函数各分配一个栈区,用于保存各自的变量。

      将主程序中的a、b作为变量传入到test函数,实际上是在test函数栈区重新生成了两个变量a、b,并将主程序中a、b变量的值赋值给test函数栈区中的a和b,即对test函数中的a和b进行初始化。可以理解,是将a=1b=2传递给了test函数,因此可以理解为将赋值传递给了test函数。只是在test栈区内,将test栈区内将test栈区a、b两变量所对应的内存地址交换了,但并未影响到主栈区内的a和b。

如何用python设置参数范围 python调参数_如何用python设置参数范围


        再看下面的代码

def test(d):    d['a'], d['b'] = d['b'], d['a']    print("test函数里,a元素的值是",d['a'], ";b元素的值是", d['b'])    print("test函数里,a元素的内存地址是",id(['a']), ";b元素的值是", id(d['b']))d = {'a': 1, 'b': 2}test(d)print("交换结束后,a元素的值是",d['a'], ";b元素的值是", d['b'])print("交换结束后,a元素的内存地址是",id(['a']), ";b元素的值是", id(d['b']))


          运行结果如下

test函数里,a元素的值是 2 ;b元素的值是 1test函数里,a元素的内存地址是 1612700978880 ;b元素的值是 140705270535840交换结束后,a元素的值是 2 ;b元素的值是 1交换结束后,a元素的内存地址是 1612700978880 ;b元素的值是 140705270535840

      和猜想的结果一样吗?

       其实也是一样的,主栈区将d = {'a': 1, 'b': 2}传递给了test栈区,只是test栈区中用d['a'], d['b'] = d['b'], d['a']进行交换时,使用的是对{'a': 1, 'b': 2}这个字典本身进行的操作,并不是对test栈区中的d进行的操作。代码修改如下

def test(d):    d['a'], d['b'] = d['b'], d['a']    print("test函数里,a元素的值是",d['a'], ";b元素的值是", d['b'])    print("test函数里,a元素的内存地址是",id(['a']), ";b元素的值是", id(d['b']))    d = Noned = {'a': 1, 'b': 2}test(d)print("交换结束后,a元素的值是",d['a'], ";b元素的值是", d['b'])print("交换结束后,a元素的内存地址是",id(['a']), ";b元素的值是", id(d['b']))


         运行结果

test函数里,a元素的值是 2 ;b元素的值是 1test函数里,a元素的内存地址是 2532485631680 ;b元素的值是 140705270535840交换结束后,a元素的值是 2 ;b元素的值是 1交换结束后,a元素的内存地址是 2532485631680 ;b元素的值是 140705270535840

        发现并没对{'a': 1, 'b': 2}本身进行修改。

        官方文档接下一句是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.

总结

1、不能将python函数的参数传递分为可变对象和不可变对象来说明

2、python参数的传递不是值传递也不是引用传递,而是通过赋值来传递

3、直接使用“=”来赋值是没有效果的

4、如果要让函数修改数据,可以将这些数据封装成为可变对象

以上即为对python参数方面的思考,不足、思虑不到之处,请斧正。

思考题

最后给出一段代码:

def test(l):    l[0] = "a"    l = [4, 5, 6]l = [1, 2, 3]test(l)print(l)

运行结果是什么? 首先大家不要运行,先预测下运行结果,并把预测结果贴在留言区,然后再运行,看看是否和预测的结果一样。