python中可变对象进行引用传递,不可变对象进行值传递
因为python中的变量其实都是指针,例如:
对于不可变对象来说:
a=1
b=1
#id(a)==id(b)
a=2
b=1
#id(a)!=id(b)
a=2
b=2
#id(a)==id(b)
内存中1或者2其实只占用了一个地址,不管多少对象引用它,都只有一个地址,都只占用了一块内存,数值的修改实际上是让变量指向了一个新的对象。
不可变可以理解为x引用的地址处的值是不能被改变的,重新赋值不可变变量实际是使其重新指向另一块内存,而不是改变原内存处存放的值。
对于可变对象来说
例如列表、集和、字典是引用类型,本身可以进行修改。
a = [1,2,3]
b = a #此处进行了引用传递
#id(a)==id(b)
b = [1,2,3]
#id(a)!=id(b)
a_last = a
a.append(4) #值的变化不会引起地址变化
#a=[1,2,3,4] a_last=[1,2,3,4]
#id(a) == id(a_last)
#id(a)==140047576300360
a = [1,2]
#id(a) == 140047576327240
#对可变对象进行重新赋值会改变对象的地址
因此可变数据的操作不能是进行直接的赋值,只能是类似与append之类的操作。
并非所有的不可变对象都是不可变的。
如前所述,Python容器比如元组,是不可变的。这意味着一个tuple的值在创建后无法更改。但是元组的“值”实际上是一系列名称,它们与对象的绑定是不可改变的。关键点是要注意绑定是不可改变的,而不是它们绑定的对象。
引用源自:
让我们考虑一个元组t =(‘holberton’,[1,2,3])
上面的元组t包含不同数据类型的元素,第一个元素是一个不可变的字符串,第二个元素是一个可变列表。元组本身不可变。即它没有任何改变其内容的方法。同样,字符串是不可变的,因为字符串没有任何可变方法。但是列表对象确实有可变方法,所以可以改变它。这是一个微妙的点,但是非常重要:不可变对象的“值” 不能改变,但它的组成对象是能做到改变的。
其实主要原因是元组内保存的是变量(也就是内存地址)。所以当变量指向对象发生变化时,如果导致变量发生变化(即不可变类型),此时元组保证该不可变类型不能修改。而如果当变量指向对象发生变化时,如果不会导致变量发生变化(即可变类型),此时元组中存储的该可变类型可以修改(因为变量本身并无变化)。
即是:
t=('hello',[1,2,3])
t[1].append(4)
# t=('hello',[1,2,3,4])
总结:
python中的不可变数据类型,不允许变量的值发生变化,如果改变了变量的值,相当于是新建了一个对象,而对于相同的值的对象,在内存中则只有一个对象,内部会有一个引用计数来记录有多少个变量引用这个对象;
可变数据类型,允许变量的值发生变化,即如果对变量进行append、+=等这种操作后,只是改变了变量的值,而不会新建一个对象,变量引用的对象的地址也不会变化,不过对于相同的值的不同对象,在内存中则会存在不同的对象,即每个对象都有自己的地址,相当于内存中对于同值的对象保存了多份,这里不存在引用计数,是实实在在的对象。
所以Python中的参数传递都是传递引用,也就是传递的是内存地址。只不过对于不可变类型,传递引用和传递值没什么区别。而对于可变类型,传递引用是真的传递内存的地址。
听说python只允许引用传递是为方便内存管理,因为python使用的内存回收机制是计数器回收,就是每块内存上有一个计数器,表示当前有多少个对象指向该内存。每当一个变量不再使用时,就让该计数器-1,有新对象指向该内存时就让计数器+1,当计时器为0时,就可以收回这块内存了。当然还有其他的GC方法,否则计数器回收,无法解决循环引用的问题。
值传递: 表示直接传递变量的值,把传递过来的变量的值复制到形参中,这样在函数内部的操作不会影响到外部的变量。
引用传递: 把引用理解为变量(标识符)与数据之间的引用关系,标识符通过引用指向某块内存地址。而引用传递,传递过来的就是这个关系,当你修改内容的时候,就是修改这个标识符所指向的内存地址中的内容,因为外部也是指向这个内存中的内容的,所以,在函数内部修改就会影响函数外部的内容。