Python传值的本质
之前就踩过is与==的坑,踩过list.copy()的坑,现在来明确一下Python传值的原理。
实际上Python传的都是引用,比如x=1是把1这个常量的引用传递给x,而1又可以被其他变量引用。至于中间计算结果中产生的1,猜想应该不会存在同一个1的地址上,有待考证。这就是Python的动态语言特性,和面向对象特性。
在这个例子中,n其实拿到了x的引用,即1,(这个赋值操作=的处理过程,是把x对对象1的引用,复制给n,让n也引用了1,同时对象1的引用计数+1,具体参见Python对象模型)。但是!n随即拿到对象10的引用,注意,10作为常量和硬编码,是被Python初始化了的,这样对10对引用传递给了n,而对象1的引用树-1。
这样就能理解传值和传引用在Python中的行为,你看这个10,就像是传值哦,不影响原变量哦,其实不是,10也是对象。但这还不够,为什么传进去的列表会被改变?列表也是对象啊。列表是可变对象,Python的可变对象,不可变对象也是个大坑。个人初步的理解是,可变对象被引用者改变的时候,不会为引用者自动产生copy(),而是改变自身,list,dict等数据结构都是这种特性。
Python的本质是传引用
然而,
在这段代码中,看上去只是用class封装了一下数据,却复制了一份新的值,10。如何解释之前n没有改变x对1的引用呢?
Python是贪婪的,如果被引用对象是可变的,Python会优先原地改变被引用对象,否则创建新对象,及其引用。因为这里a成为了class中字典的一员,字典中的对象是可变的,一个class就是一个大字典,用__dict__就能看到。所以a其实成了新的引用,引用了10。
有个逻辑必须阐明清楚,是引用者不可改变,还是被引用的东西不可改变。在上面例子中是后者,即list是引用者,是可变的,是对很多很多数的引用,被引用的数是不可改变的。以及传递参数实际上是传引用,局部实参如果获得传入对象的引用,而传入对象可变,那么实参就会进行原地改变操作。在class中,a在class的__dict__里面,因此变得可变了,于是实参args.a原地改变a,使其引用新的对象10。
结论,所有传递的参数,都是对象,都是引用,都是指针(不严谨)!Python是没有形参和实参的概念的,动态特性为我们省了很多诸如C++传值传引用传指针的比较和区别,省了很多事儿,也隐藏了很多坑,比如传列表,如果理解为传值那就是大坑,因为实际上传的是引用,函数块中对列表的处理全部会直接影响列表。这种情况下药么list.copy(),要么把被处理列表的数据和方法封装成类,便于工程上的理解(这很C++ Style)。
参考资料
https://www.techforgeek.info/python_pass_para_by_reference.html