对象是被分配的一块内存,存储其所代表的值
引用是自动形成的从变量到对象的指针
特别注意: 类型属于对象,不是变量
>>> c = 17 #1 数字17就是一个对象,实实在在存在计算机内存中
>>> d = c #2 c 和 d 都是对象17的一个引用,c指向17,d也是
>>> id(c) #3
1462698960
>>> id(d) #4
1462698960
在#1 处我们定义了各一个变量c,c指向了17(把17赋值给c),对象17的一个引用c
然后在#2处,又定义了一个变量d ,把c赋值给了d,接着#3、#4查看了c、d的 id 相同,
发现是同一个对象(17),对象17的引用+1
引用:
对象17的引用现在有两个了
变量:
在内部,变量事实上是到对象内存空间的一个指针
1.2.2 怎么减少对象的引用
- 将变量引用指向其他对象
>>> c = 17
>>> d = c
>>> id(c) #1
1462698960
>>> id(d) #2
1462698960
>>> c = "yue" #3
>>> id(c) #4
612496081896
>>> d #5
17
可以看到#1、#2处c、d都还是对象17的引用,当#3处把变量c 指向新对象字符串"yue" 时,#4处发现变量c指向的对象id变了,的确不是17了,所以对象17的引用 -1 如下图
注意:这儿改变了c的引用,可是#5处d却没有跟着c变,还是对象17
同理当你再把d指向其他对象时,对象17的引用就减为零,当Python来检查时,就会回收这块内存了
2.删除变量(引用)
>>> del d
>>> d
Traceback (most recent call last):
File "<stdin>", line 1, in <modul
NameError: name 'd' is not defined
不啰嗦,这样对象17就彻底被删除了,上图时对象17只剩下一个变量引用d。
同理对于函数,定义函数时,函数名就是一个引用,当其他地方调用函数时,引用+1,调用结束 -1 。在函数的命名空间中可以查到这些,详情看我这篇文章
python的内存回收就到这儿:总结:回收机制为判断对象 引用是否为0,如果为零就回收内存到自己申请的内存空间,不是计算机硬盘。
>>> a = (1, 'one')
>>> id(a)
612494666568 #1
>>> a[0] = 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
>>> a[0]
1
>>> a = (2, 'two')
>>> id(a)
612494666824 #2
#---------------------------------------------
>>> a = 'findxgo' #3
>>> id(a)
612496082848
>>> a.replace('x','--X--') #4
'find--X--go'
>>> id(a) #5
612496082848
>>> a = a.replace('x', '-X-') #6
>>> id(a)
612496086704
在#3出定义了字符串a,#4处替换x,得到一新字符串,但是原字符串还是#5id没变,当#6把替换的字符串赋值给变量a,a的引用指向了替换后新字符串
如果删掉c后, 不会影响d
拷贝概念的引入就是针对:可变对象的共享引用潜在的副作用而提出的。
>>> list_1 = [5, 2, 1]
>>> L2 = list_1 #1 将list_1赋值给L2
>>> list_1,L2
([5, 2, 1], [5, 2, 1])
>>> list_1[2] = '01314' #2 修改list_1 索引2处的值
>>> list_1,L2
([5, 2, '01314'], [5, 2, '01314'])
可以看到#1 上面定义一个列表,赋值给L2后,L2、list_1对应完全一样的值(列表)事实上,他两的确对应着一块内存,你可以自己去查id,是那块内存(列表)的两个引用
当你去在list_1,或者L2进行操作时,改变了对应内存的值,所以#2下面两个值都变了。python中同一块内存(对象)的不同引用改变对象所以引用都会被影响。
同理字典:通过自己哈希表将key计算后得到的内存地址就是存放value的地方,当你用如上同样的方式改变哪儿的值,所有引用都会被影响。
>>> list_1 = [5, 2, 1]
>>> L2 = list_1[:] #1 此处完全切片,复制
>>> list_1,L2
([5, 2, 1], [5, 2, 1])
>>> id(list_1) #2 查看id
612496051784
>>> id(L2) #3
612496051720
如上:#1处完全切片也可以L2 = list_1[0: -1]
是一样的,#2,#3处可以看见,id不同,就不是同一个对象,只是里面的value相同而已
同理copy模块的copy方法,这是浅拷贝。
>>> import copy
>>> dict_1 = {'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}
>>> D2 = copy.copy(dict_1) #浅拷贝:只拷贝顶级的对象,也说:父级对象
>>> D3 = copy.deepcopy(dict_1) #深拷贝:拷贝所有对象,顶级对象及其嵌套对象。或者说:父级对象及其子对象
>>> print("源:{0: ^18}
浅拷贝:{1}
深拷贝:{2}".format(id(dict_1),id(D2),id(D3)))
源: 37811303432
浅拷贝:37813197256
深拷贝:37813160264
2.改变源顶级对象,深浅拷贝不会变
>>> dict_1['copy'] = 'n_copy'
>>> dict_1;D2;D3
{'copy': 'n_copy', 'deepcopy': ['deep', '第二层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}
3.改变源嵌套对象,浅拷贝变了,深拷贝不变
>>> dict_1['deepcopy'][1] = '嵌套层'
>>> dict_1;D2;D3
{'copy': 'n_copy', 'deepcopy': ['deep', '嵌套层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '嵌套层', ' 深拷贝']}
{'copy': '浅拷贝', 'deepcopy': ['deep', '第二层', ' 深拷贝']}
这儿的浅拷贝,只拷贝了父级对象,在'deepcopy'对应的哪儿就是只拷贝了内存地址,而深拷贝还要去内存地址拷贝内容回来赋值
原理看到这儿,差不多也懂了,就不罗嗦了!
- 深浅拷贝都是对源对象的复制,占用不同的内存空间
- 如果源对象只有一级目录的话,源做任何改动,不影响深浅拷贝对象
- 如果源对象不止一级目录的话,源做任何改动,都要影响浅拷贝,但不影响深拷贝
- 序列对象的切片其实是浅拷贝,即只拷贝顶级的对象
一个有意思的练习题
import copy
a = [1,2,3,[4,5],6]
b=a
c=copy.copy(a)
d=copy.deepcopy(a)
b.append(10)
c[3].append(11)
d[3].append(12)
a,b,c,d分别为什么?
答案我放评论