文章目录
basic concepts:
1,变量:是一个系统表的元素,拥有指向对象的连接空间
2,对象:被分配的一块内存,存储其所代表的值
3,引用:是自动形成的从变量到对象的指针
4,注意:类型(int类型,long类型(python3已去除long类型,只剩下int类型的数据))属于对象(object),不是变量(instance)
5,不可变对象:一旦创建就不可修改的对象,包括str,tuple,int,float
6,可变对象:可以修改的对象,包括list,dict,set。
赋值
不可变对象(以str为例)
>>> a = 'hello'
>>> b = 'hello'
>>> c = a
>>> [id(x) for x in [a,b,c]]
[2890953102144, 2890953102144, 2890953102144]
由以上指令中,我们可以发现a, b, c三者的地址是一样的。所以以上赋值的操作就相当于c = a = b = ‘hello’。
赋值是系统先给一个变量或者对象(这里是’hello’)分配了内存,然后再将地址赋给a, b, c。所以它们的地址是相同的。
即对于b的情况,由于内存中已经有了‘hello’,因此不会再创建新的字符串,而是直接把b也指向同一个地址
可变对象(以list为例,并且list元素的内容和上面相同)
>>> a = ['hello']
>>> b = ['hello']
>>> c = a
>>> [id(x) for x in [a,b,c]]
[2890966289928, 2890966289672, 2890966289928]
因为str是不可变的,所以同样是’hello’,但是list是可变的,所以必须分配两个地址。
修改:
不可变对象(接上面str例)
>>> a = ['hello']
... b = ['hello']
... c = a
>>> [id(x) for x in [a,b,c]]
[2890966288776, 2890966288968, 2890966288776]
>>> id(a[0])
2890953102144
>>> a[0] = 'world'
>>> id(a[0])
2890966747040
>>> [id(x) for x in [a,b,c]]
[2890966288776, 2890966288968, 2890966288776]
>>> print(a,b,c)
['world'] ['hello'] ['world']
这时a的地址和值变了,但是b, c地址和值都未变。因为str的不可变性,a要重新赋值则需重新开辟内存空间,所以a的值改变,a指向的地址改变。b, c由于’hello’的不变性,不会发生改变。
即b c所指向的字符串没发生变化,而a的已经指向新的了
可变对象(接上面list的例子)
>>> a = ['hello']
... b = ['hello']
... c = a
... [id(x) for x in [a,b,c]]
>>> [id(x) for x in [a,b,c]]
[2890966289608, 2890966289736, 2890966289608]
>>> a[0] = 'world'
>>> [id(x) for x in [a,b,c]]
[2890966289608, 2890966289736, 2890966289608]
>>> print(a, b, c)
['world'] ['hello'] ['world']
这时a, c的值和地址均改变,但二者仍相同,b不改变。由于list的可变性,所以修改list的值不需要另外开辟空间,只需修改原地址的值。所以a, c均改变。
以上关系可以同下面的图表示
可变对象list的情况
修改a[0]后:
不可变对象str的情况:
对a进行赋值a='world’后
总结
参考:python基础(5):深入理解 python 中的赋值、引用、拷贝、作用域
对于不可变对象和可变对象来说,浅复制都是复制的引用,只是因为复制不变对象和复制不变对象的引用是等效的(因为对象不可变,当改变时会新建对象重新赋值)。所以看起来浅复制只复制不可变对象(整数,实数,字符串等),对于可变对象,浅复制其实是创建了一个对于该对象的引用,也就是说只是给同一个对象贴上了另一个标签而已。
无论是可变对象还是不可变对象,变量间的赋值都是直接复制引用(就是给原来的内存空间贴新标签)!!
也即复制箭头,在图1中就是a到list_1的引用,图3中就是a到hello的箭头
但由于对于不可变对象,重新进行赋值(即改变的时候)会新建对象进行赋值,因此看起来“对于不可变对象的赋值,实际是复制了一个新的对象”
slicing [] 切片操作:shallow copy
浅拷贝,也即顶层复制,只对最外层的开辟新的内存空间,而内层元素不会重新开辟内存空间。
>>> a = ['hello', [123, 234]]
>>> b = a[:]
>>> [id(x) for x in a,b]
>>> [id(x) for x in [a,b]]
[2890966288392, 2890966392264]
>>> [id(x) for x in a]
[2890953102144, 2890966289288]
>>> [id(x) for x in b]
[2890953102144, 2890966289288]
>>> a[0] = 'world'
>>> a[1].append(345)
>>> print('a = ', a, '\n\r', 'b = ', b)
a = ['world', [123, 234, 345]]
b = ['hello', [123, 234, 345]]
修改后:
a中第一个元素str改变,但是b中未改变;a中第二个元素改变,b中也改变。这就符合不可变的对象修改会开辟新的空间,可变的对象修改不会开辟新空间,而是直接在原地址上进行操作。
这里的顶层,也就是list_0复制出一个list_1,并把list_1的地址赋给b,但是内层元素的指向是相同的
deep copy(本质是递归到最底层,把所有引用都复制)
>>> from copy import deepcopy
>>> a = ['hello', [123, 234]]
>>> b = deepcopy(a)
>>> [id(x) for x in (a, b)]
[2890966267016, 2890966327368]
>>> [id(x) for x in a]
[2890953102144, 2890966327880]
>>> [id(x) for x in b]
[2890953102144, 2890966327496]
>>> [id(x) for x in a[1]] # 看内部list的指向
[1833270080, 1833273632]
>>> [id(x) for x in b[1]] #看内部list的指向
[1833270080, 1833273632]
>>> a[0] = 'world'
>>> a[1].append(345)
>>> print('a = ', a, '\n\r', 'b = ', b)
a = ['world', [123, 234, 345]]
b = ['hello', [123, 234]]
可见b的值未改变
上述关系又下图表示:
修改后
由于内层的list也同样复制了一份(即list_inner_1,并让b[1]指向它),第二条赋值a[1].append(345) 是对list_inner_0进行操作,由于是可变对象,因此直接在原内存地址上进行修改。
总结
-
赋值是将一个对象的地址赋值给一个变量,让变量指向该地址(旧瓶装旧酒)。
-
浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素(新瓶装旧酒)。
-
深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说(新瓶装新酒)。