不可变对象与可变对象分类
什么是可变对象和不可变对象
python中,一切皆对象,对象有3个重要的因素:变量、取值、id。
比如赋值语句:a=123,变量a指向123,123是取值,id是123这个数值在内存中的地址。
对于列表,表示如下:
可变对象和不可变对象的区别在于是否可以“原地”修改变量的取值。不可变对象,不能“原地”修改变量的取值。可变对象,可“原地”修改变量的取值。
不可变对象不会“原地”修改变量的取值
不可变对象包括:数字、字符串、元组。是客观存在的,当试图通过赋值等操作,修改变量的取值时,并没有真正的改变变量的取值,而是将变量指向了新的取值。
关于不可变对象,最经常的理解是:不可变对象,不会重新分配新的内存空间。这种说法,其实并不完全对。
- 没有重新分配新的内存空间
>>> a = 123
>>> b = 123
>>> print(id(a), id(b))
4537342432 4537342432
>>>
>>> astr = "Hello"
>>> bstr = "Hello"
>>> print(id(astr), id(bstr))
4541756080 4541756080
如上,数字a和b,所指向的内存空间一样;字符串astr和bstr指向的内存空间也一样,并没有重新在分配新的内存空间。
2. 重新分配内存空间,python的intern机制
然而,并不是所有的不可变对象,都不分配内存空间。下面的例子中,可以看到浮点数和字符串,内存地址并不一致,有重新分配内存空间。
>>> c = 1.1
>>> d = 1.1
>>> print(id(c), id(d))
4541726160 4541726128
>>>
>>> cstr = "hello world"
>>> dstr = "hello world"
>>> print(id(cstr), id(dstr))
4541756336 4541756144
这就是python的intern机制:如果缓存中有内存地址,直接取缓存中的内存地址;如果没有,重新分配新的内存地址。
不可变对象-数:只有整数,且范围在[-5, 256]这一范围内,不用重新分配内存空间,直接取缓存中内存空间即可,包括bool类型的True和False,直接取缓存内存地址;对于浮点数,重新分配新的内存空间。
不变变对象-字符串:只有对类似标识符的字符串才有缓存,不用重新分配内存空间;其他字符串需重新分配内存空间。
不可变对象-元组:重新分配内存空间,用数组来实现。
无论是否重新分配内存空间,不可变对象都是客观存在,对不可变对象的变量操作,并没有改变不可变对象的取值,而是变量重新指向了新的内存空间(包括缓存和新开辟的内存)。
# astr指向了新的内存空间,并没有直接在“Hello”所在内存空间内改变
>>> astr = "Hello"
>>> print(id(astr))
4541757168
>>> astr = astr + " world"
>>> print(id(astr))
4541756336
任何试图“原地”修改不可变对象的操作,将不可行。
>>> astr = "Hello"
>>> print(id(astr))
4541756080
>>> astr[0] = "T"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'str' object does not support item assignment
>>> atuple = ("a", "b", "c")
>>> atuple[0] = "M"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
-
可变对象,"原地"修改
- 可变对象,都是重新分配新的内存空间。
>>> clist = ["a", "b", "c"]
>>> dlist = ["a", "b", "c"]
>>> print(id(clist), id(dlist))
4541747008 4541755456
2. 对不可变对象的操作,都是"原地"进行
>>> clist = ["a", "b", "c"]
>>> print(id(clist))
4541747008
>>> clist.append("m")
>>> print(id(clist))
4541747008
赋值
可变对象和不可变对象,都是将目前所指的内存地址复制给变量,也就是说,该取值会新增一个引用对象。不同在于,不可变对象,其中的一个变量修改了,会指向新的内存空间,而可变对象是“原地”修改,任一变量的修改,都会影响到对象的修改。
不可变对象赋值:a = 123, b = a,此时变量a和b指向新的相同的内存空间:
可变对象赋值:alist = ["a", "b", "c"],blist = alist,此时变量alist和blist指向的也是相同的内存空间:
但是,当修改不可变对象的其中取值时,变量会指向新的内存空间,并不会修改原来的取值。
可变对象,修改任一一个变量,都会影响到原来的取值,因为都是“原地”修改,如下修改alist或blist任一,都会更改列表。
is和==,type
is需是内存地址和取值都相同
>>> a = 257
>>> b = 257
>>> a is b
False
>>> a == b
True
上面例子中,a=257,不在[-5, 256]这一整数范围内,需要重新分配新的内存空间,所以a is b得到的结果是False
==是取值相同
列表的赋值
- lst_copy = lst,列表lst和lst_copy 是同一个id地址
- lst_copy = lst[:],列表lst和lst_copy不是同一个id地址,新开辟了新的内存空间。
浅拷贝
对不可变对象:不分配内存空间
对可变对象:只分配父类内存空间,没有分配其子类的内存空间。
import copy
alist = ["a", "b", [1, 2, 3]]
blist = copy.copy(alist)
alist[2].append(4)
print(id(alist), id(blist))
print(id(alist[2]))
print(id(blist[2]))
调用copy.copy(x)方法,虽然alist和blist有不同的内存地址,但是其元素[1, 2, 3]并没有重新分配新的内存空间。可以图解如下:
深拷贝
复制内存空间,包括复制了父类的内存空间及其子类的内存空间。深度拷贝后,完全是2个独立的对象,不会互相影响。
import copy
alist = ["a", "b", [1, 2, 3]]
blist = copy.deepcopy(alist)
alist[2].append(4)
print(id(alist), id(blist))
print(alist)
print(blist)