有关Python参数的思考

有关Python参数的思考

小编 DevOps视角

引子

    在群里和人讨论了有关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 = 1
b = 2
test(a , b)
print("交换结束后,a的值是", a , ";变量b的值是", b)
print("交换结束后,a的内存地址是", id(a) , ";b的内存地址是", id(b))
       运行的结果如下

test函数里,a的值是 2 ;b的值是 1
test函数里,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=1和b=2传递给了test函数,因此可以理解为将赋值传递给了test函数。只是在test栈区内,将test栈区内将test栈区a、b两变量所对应的内存地址交换了,但并未影响到主栈区内的a和b。

有关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元素的值是 1
test函数里,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 = None

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元素的值是 1
test函数里,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)

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

猜你喜欢

转载自blog.51cto.com/15127511/2657825
今日推荐