深浅拷贝
def to_id(lis):
print([id(i) for i in lis])
def print_sm():
print('al', al)
print(to_id(al))
print('*' * 20)
print('bl', bl)
print(to_id(bl))
print('*' * 20)
print('cl', cl)
print(to_id(cl))
print('*' * 20)
import copy
al = [[1,2,3],[2],[3],'123']
bl = copy.copy(al)
cl = copy.deepcopy(al)
print("before=>" )
print_sm()
# print(bl)
# print(cl)
al[0][0] = 0
al[2] = None
al[3] = 'asd'
print("after=>")
print_sm()
运行结果:
before=>
al [[1, 2, 3], [2], [3], '123']
[35976216, 35976136, 35941792, 9177216]
None
********************
bl [[1, 2, 3], [2], [3], '123']
[35976216, 35976136, 35941792, 9177216]
None
********************
cl [[1, 2, 3], [2], [3], '123']
[35940872, 35978536, 35978616, 9177216]
None
********************
after=>
al [[0, 2, 3], [2], None, 'asd']
[35976216, 35976136, 1353477640, 9177696]
None
********************
bl [[0, 2, 3], [2], [3], '123']
[35976216, 35976136, 35941792, 9177216]
None
********************
cl [[1, 2, 3], [2], [3], '123']
[35940872, 35978536, 35978616, 9177216]
None
********************
首先,对赋值操作我们要有以下认识:
- 赋值是将一个对象的地址赋值给一个变量,让变量指向该地址( 旧瓶装旧酒 )。
- 修改不可变对象(
str
、tuple
)需要开辟新的空间 - 修改可变对象(
list
等)不需要开辟新的空间
浅拷贝:copy()
浅拷贝仅仅复制了容器中元素的地址
这里可以看出,未修改前,a
和b
中元素的地址都是相同的,不可变的字符串
和可变的list
地址都一样,说明浅拷贝知识将容器内的元素的地址复制了一份。这可以通过修改后,b
中字符串没改变,但是list
元素随着a
相应改变得到验证。
浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )。
深拷贝:deepcooy()
深拷贝,完全拷贝了一个副本,容器内部元素地址都不一样
这里可以看出,深拷贝后,a
和b
的地址以及a
和b
中的元素地址均不同,这是完全拷贝的一个副本,修改a
后,发现b
没有发生任何改变,因为b
是一个完全的副本,元素地址与a
均不同,a
修改不影响b
。
深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )。
番外:
在看copy的原码时候无意间发现一个
_copy_dispatch = d = {}
表示很惊讶还可以这样用 Twt
然后做了个测试
dic = b = {}
dic['aa'] = 1
b['ss'] = 11
print(dic)
print(b)
输出结果如下:
{'aa': 1, 'ss': 11}
{'aa': 1, 'ss': 11}
这样看不出什么我们继续试,反正试试又不会怀孕
def dic_id(dic):
print({id(i):id(v) for i,v in dic.items()})
dic = b = {}
dic['aa'] = 1
b['ss'] = 11
print(dic)
print(dic_id(dic))
print(b)
print(dic_id(b))
结果如下
{'aa': 1, 'ss': 11}
{41497248: 1720534672, 38312224: 1720534832}
None
{'aa': 1, 'ss': 11}
{41497248: 1720534672, 38312224: 1720534832}
None
这是一个浅拷贝。QwQ
没什么大不了的 只是一个拷贝,无聊嘛~我就del了一下
def dic_id(dic):
print({id(i):id(v) for i,v in dic.items()})
dic = b = {}
dic['aa'] = 1
b['ss'] = 11
print(dic)
print(dic_id(dic))
del dic
print(b)
print(dic_id(b))
结果如下:
{'aa': 1, 'ss': 11}
{38941312: 1717126800, 37984544: 1717126960}
None
{'aa': 1, 'ss': 11}
{38941312: 1717126800, 37984544: 1717126960}
None
内存地址还在占用着 没删掉,也没有回收。嗯 ~ 按照我的理解就是:python的内存回收机制是根据变量名来回收的。当一块内存地址没有变量名指向的时候才会回收。所以python的del是删除变量名而不是删除该内存地址
Twt 挂上Google大佬的精确回答:
作者:东皇Amrzs
链接:http://www.jianshu.com/p/1e375fb40506
來源:简书
引用计数机制:
python里每一个东西都是对象,它们的核心就是一个结构体:PyObject
typedef struct_object {
int ob_refcnt;
struct_typeobject *ob_type;
} PyObject;
PyObject是每个对象必有的内容,其中ob_refcnt就是做为引用计数。当一个对象有新的引用时,它的ob_refcnt就会增加,当引用它的对象被删除,它的ob_refcnt就会减少
#define Py_INCREF(op) ((op)->ob_refcnt++) //增加计数
#define Py_DECREF(op) \ //减少计数
if (--(op)->ob_refcnt != 0) \
; \
else \
__Py_Dealloc((PyObject *)(op))
当引用计数为0时,该对象生命就结束了。
引用计数机制的优点:
- 简单
- 实时性:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
引用计数机制的缺点:
- 维护引用计数消耗资源
- 循环引用
list1 = []
list2 = []
list1.append(list2)
list2.append(list1)
list1与list2相互引用,如果不存在其他对象对它们的引用,list1与list2的引用计数也仍然为1,所占用的内存永远无法被回收,这将是致命的。
对于如今的强大硬件,缺点1尚可接受,但是循环引用导致内存泄露,注定python还将引入新的回收机制。(标记清除和分代收集)