深浅拷贝及番外引用计数机制

来源:互联网 发布:ds数据精灵安卓破解版 编辑:程序博客网 时间:2024/05/21 07:15

深浅拷贝

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 copyal = [[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] = 0al[2] = Noneal[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********************

首先,对赋值操作我们要有以下认识:

  1. 赋值是将一个对象的地址赋值给一个变量,让变量指向该地址( 旧瓶装旧酒 )。
  2. 修改不可变对象(strtuple)需要开辟新的空间
  3. 修改可变对象(list等)不需要开辟新的空间

浅拷贝:copy()

浅拷贝仅仅复制了容器中元素的地址

这里可以看出,未修改前,ab中元素的地址都是相同的,不可变的字符串
和可变的list地址都一样,说明浅拷贝知识将容器内的元素的地址复制了一份。这可以通过修改后,b中字符串没改变,但是list元素随着a相应改变得到验证。

浅拷贝是在另一块地址中创建一个新的变量或容器,但是容器内的元素的地址均是源对象的元素的地址的拷贝。也就是说新的容器中指向了旧的元素( 新瓶装旧酒 )。

深拷贝:deepcooy()

深拷贝,完全拷贝了一个副本,容器内部元素地址都不一样

这里可以看出,深拷贝后,ab的地址以及ab中的元素地址均不同,这是完全拷贝的一个副本,修改a后,发现b没有发生任何改变,因为b是一个完全的副本,元素地址与a均不同,a修改不影响b

深拷贝是在另一块地址中创建一个新的变量或容器,同时容器内的元素的地址也是新开辟的,仅仅是值相同而已,是完全的副本。也就是说( 新瓶装新酒 )。

番外:
在看copy的原码时候无意间发现一个

_copy_dispatch = d = {}

表示很惊讶还可以这样用 Twt
然后做了个测试

dic = b = {}dic['aa'] = 1b['ss'] = 11print(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'] = 1b['ss'] = 11print(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'] = 1b['ss'] = 11print(dic)print(dic_id(dic))del dicprint(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还将引入新的回收机制。(标记清除和分代收集)
原创粉丝点击