python3

xiaoxiao2021-03-01  75

1.动态类型

1. 对象是存储在内存中的实体。但我们并不能直接接触到该对象。

2. 引用与对象分离是动态类型的核心。

(一)不可变数据类型:

# --------------------引例1 a = 1 b = a a = a + 2 print(a, b) OUTPUT: --> 3 1 # --------------------引例2 lt = [1, 2, 3] lt2 = lt lt = 4 print(lt, lt2) OUTPUT: --> 4 [1, 2, 3] # 说明: 1.开始a和b为指向1的两个引用 2.第三个表达式中a重新赋值,指向了新的对象3 # 总结: 即使多个引用指向同一对象,若一个引用值发生变化,那么实际上是该引用指向一新引用,并不影响其他的引用的指向。

(二)可变数据类型:

以列表为例:

1. 列表相当于一个引用的集合,每一个元素相当于一个引用(lt[0], lt[1], lt[2])

2. 下部代码中,lt[0] = 11这一操作,改变的是第一个元素(引用)的指向,而不是lt的指向。故所有指向该lt对象的引用聚会

    受到影响。

 

lt = [1, 2, 3] lt2 = lt lt[0] = 11 print(lt, lt2) OUTPUT: --> [11, 2, 3] --> [11, 2, 3]

2.python内存回收机制

(一)对象的内存使用

1. python为动态类型编程语言。对象与引用相分离。

2. id(对象):查看对象的内存地址

3. python当中会缓存整数、浮点数、字符串、空元组、空集合,并不反复的创建和销毁。例:当创建多个引用引用1时,实际上这些

    引用均指向同一个对象。

4. is 关键字用于判断两个引用的对象是否相同。

# python当中会缓存整数、短小字符,并不反复的创建和销毁。例:当创建多个引用引用1时,实际上这些引用均指向同一个对象。 # --------------------1.整数:True a = 1 b = 1 print(id(a), id(b)) print(a is b) # --------------------2.浮点数:True a = 1.0 b = 1.0 print(id(a), id(b)) print(a is b) # --------------------3.短字符串:True a = "good" b = "good" print(id(a), id(b)) print(a is b) # --------------------4.字符串:True a = "very good morning this is linux 123 456 789 10111213" b = "very good morning this is linux 123 456 789 10111213" print(id(a), id(b)) print(a is b) # --------------------5.列表:False a = [1, 2] b = [1, 2] print(id(a), id(b)) print(a is b) # --------------------6.元组(非空):False a = (1, 2) b = (1, 2) print(id(a), id(b)) print(a is b) # --------------------7.集合:False a = set([1, 2]) b = set([1, 2]) print(id(a), id(b)) print(a is b) # --------------------8.字典:False a = {"name": "mx"} b = {"name": "mx"} print(id(a), id(b)) print(a is b)

(二)引用计数(跟踪和回收垃圾)

1. 可通过sys包中的getrefcount(引用名)来查看某个对象的引用计数

2. 当将某个引用作为实参传递给getrefcount()时,参数实际上创建了一个临时的引用。因此,引用计数结果比实际值多1

3. 对于python的容器(container)对象,如:列表、字典等,其内部包含的并不是对象,而是对象的引用。

4. 词典对象用于记录所有全局变量的引用。可通过内置函数globals()查看该词典

5. 容器对象的引用可能会构成很复杂的拓扑结构。可通过objgraph包中的show_refs()函数来进行查看

6.objgraph包的安装(windows):pip install xdot   /   pip install objgraph

展示(# 3 中对象引用的拓扑结构):

 

from sys import getrefcount import objgraph lt1 = [0, 8, 2, 4] lt2 = lt1 # 1.查看对象 [0, 8, 2, 4]的引用计数 print(getrefcount(lt1) - 1) # 2.查看词典(记录全局变量的引用)对象 print(globals()) # 3.查看容器对象引用的拓扑结构 x = [1, 9, 9, 5] y = [x, dict(key1=x)] z = [y, (x, y)] # def show_refs(objs, max_depth=3, extra_ignore=(), filter=None, too_many=10, # highlight=None, filename=None, extra_info=None, # refcounts=False, shortnames=True, output=None) # max_depth / too_many: 限制图形的深度和宽度 # extra_ignore / fileter:删除图标中不需要的对象 # hightlight:以蓝色突出显示某些图形结点 # filename / output:``output`` and ``filename`` should not both be specified. # extra_info:显示对象的额外信息 # refcounts: 是否查看对象引用计数 objgraph.show_refs([z], filename="ref_topo.dot")

(三)引用减少

 python内置关键字del删除的是对象的引用,而不是内存中的对象。

from sys import getrefcount a = [1, 2, 3] b = a c = a print(getrefcount(a)) del c print(getrefcount(a)) del b print(getrefcount(a))

(四)垃圾回收

1. 当python某个对象引用计数为0,说明该对象无引用。python会启动“垃圾回收”,将无用的对象清除(从内存中清除)

问题:

         频繁的垃圾回收,会大大降低Python的工作效率。故,python只会在特定的条件下,自动启动垃圾回收。

2. python通过阙值( |“分配对象次数” - “取消分配对象次数”| )来判断是否进行垃圾回收。(高于阙值则进行垃圾回收)

3. 可通过gc包get_threshold()函数查看阙值大小;set_threshold()函数设置阙值大小。

from sys import getrefcount import gc a = [1, 2, 3] b = a c = b print(getrefcount(a) - 1) print(gc.get_threshold()) gc.set_threshold(300) print(gc.get_threshold()) OUTPUT: --> (700, 10, 10) --> (300, 10, 10) # ---------------------------说明-------------------------------- # # 结果中的第一个参数代表阈值的大小 # 第二个参数代表“每10次0代垃圾回收,会配合一次1代垃圾回收”(分代回收) # 第三个参数代表“每10次1代垃圾回收,会配合一次2代垃圾回收”(分代回收) # 后两个参数同样是通过gc.set_threshold()函数进行修改 # -----------------后两个参数涉及到分代回收的问题------------------- #

(五)分代回收(以空间换时间进一步提高垃圾回收效率)

1. python同时采用分代回收策略。该策略假设:存活时间越久的对象,越不可能在后边的程序当中编程垃圾

2. python将所有对象分为0, 1, 2三代对象。

3. 所有的新建对象都是0代对象。当某一代对象经历过垃圾回收,依然存活,那么它就被归入下一代对象。垃圾回收启动

    时,一定会扫描所有的0代对象。如果0代经过一定次数垃圾回收,那么就启动对0代和1代的扫描清理。当1代也经历了一

    定次数的垃圾回收后,那么会启动对0,1,2,即对所有对象进行扫描

4. 查看和修改如(四)中代码所示。

(六)孤立的引用环

lt1 = [1, 2, 3] lt2 = [lt1] lt1.append(lt2) del lt1 del lt2

说明:

        上边代码块中创建了两个列表对象lt1, lt2 。这两个列表对象相互引用,形成孤立的引用环。当删除lt1, lt2的时候,以上两个列表对象在程序中将无法被调用,但是其实际的引用计数并不为0,不会被垃圾回收。

        为了回收这样的引用环,python复制每一个对象的引用计数(lt1:1, lt2:1)。然后,python遍历所有的引用环涉及到的对象,该处仅有lt1 和lt2 ,当遍历到lt1时,由于lt1引用了lt2, 故将lt2的引用计数减1。同理,当遍历到lt2的时候将lt1的引用计数减1,结果他们的值都为0,最后将不为0的对象保留,为0 的对象进行垃圾回收。

       但是这样就有一个问题,假设对象A有一个对象引用C,而C没有引用A,如果将C计数引用减1,而最后A并没有被回收,显然,我们错误的将C的引用计数减1,这将导致在未来的某个时刻出现一个对C的悬空引用。这就要求我们必须在A没有被删除的情况下复原C的引用计数,如果采用这样的方案,那么维护引用计数的复杂度将成倍增加。

        因此,“标签-清除”方法显得更好。

(七)标签 - 清除法

       首先,他先划分出两拨,一拨叫root object(存活组),一拨叫unreachable(死亡组)。然后,他把各个对象的引用计数复制出来,对这个副本进行引用环的摘除。摘除完毕,a和c的引用计数副本为0,b的引用计数副本为1,则将那么先把副本为非0的放到存活组(b),副本为0的打入死亡组(a, c)。那么此时若将引用计数为0的对象从内存中清除,则b在引用c的时候就会产生对c的悬空引用。为解决这种问题,python会在存活组中对每个对象都分析一遍,由于目前存活组只有b,那么他只对b分析,因为b要存活,所以b里的元素也要存活,于是在b中就发现了原a所指向的对象,于是就把他从死亡组中解救出来。至此,进过了一审和二审,最终把所有的任然在死亡组中的对象通通杀掉,而root object继续存活。b所指向的对象引用计数依然是2,原c所指向的对象的引用计数仍然是1。

 

转载请注明原文地址: https://www.6miu.com/read-3649982.html

最新回复(0)