CDA117513

2022-02-10   阅读量: 436

Python Python概念 Python基础 Python内存和回收 Python变量

Python概念篇1

扫码加入数据分析学习群

目录:

1.Python:标识符、保留字(关键字)、变量、对象

2.Python:内存和垃圾回收机制


正文:

  1. Python:标识符、保留字(关键字)、变量


(1)标识符:

标识符的主要作用是作为变量、函数、类、模块以及其他对象的名称。

>>> 命名原则:

  • 标识符是由字符(A~Z 和 a~z)、下划线和数字组成,但第一个字符不能是数字。

  • 标识符不能和 Python 中的保留字相同。有关保留字,后续章节会详细介绍。

  • Python中的标识符中,不能包含空格、@、% 以及 $ 等特殊字符。

备注:在Python3.x的版本中,非ASCII标识符也是允许的。


(2)保留字(关键字)

保留字(也可以叫关键字),Python中有一些单词被我赋予了特定的意义,这些单词在给你的任何对象起名字的时候都不能用

常用关键字如下
'and', 'as', 'assert', 'break', 'class', 'continue', 'def',
'del', 'elif', 'else', 'except', 'exec', 'finally', 'for', 'from',
'global', 'if', 'import', 'in', 'is', 'lambda', 'not', 'or', 'pass',
'print', 'raise', 'return', 'try', 'while','with', 'yield'


可以在python编辑器中查看关键字:

import keyword
print(keyword.kwlist)


(3)变量

Python中变量由3部分组成:
标识:标识对象所存储的内存地址,使用内置函数id(obj)来获取
类型:表示对象的数据类型,使用内置函数type(obj)来获取
值:表示对象所存储的具体数据,使用print(obj)可以将值进行打印输出

image.png

变量多次赋值后,指向新的内存空间

image.png


(4)对象:object

所有的数据类型,值,变量,函数,类,实例等等一切可操作的基本单元在 Python 都使用对象(Object)表示。每个对象有三个基本属性:ID,类型和值,也即有一块内存中存储了一个对象,这块内存中一定存有这三个属性。






2.Python:内存管理和垃圾回收机制

(1)如何理解Python的内存管理和垃圾回收Python什么时候会出现垃圾回收?

>>> Python的内存和垃圾回收:

Python解释器在执行到定义变量的语法时,会申请内存空间来存放变量的值,而内存的容量是有限的,这就涉及到变量值所占用内存空间的回收问题,当一个变量值没有用了(简称垃圾)就应该将其占用的内存给回收掉,那什么样的变量值是没有用的呢?

>>> Python的垃圾回收:

单从逻辑层面分析,我们定义变量将变量值存起来的目的是为了以后取出来使用,而取得变量值需要通过其绑定的直接引用(如x=10,10被x直接引用)或间接引用(如l=[x,],x=10,10被x直接引用,而被容器类型l间接引用),所以当一个变量值不再绑定任何引用时,我们就无法再访问到该变量值了,该变量值自然就是没有用的,就应该被当成一个垃圾回收。正如我们在上文举的例子,当name被重新赋值为艾瑞斯时,会生成一个新的ID,这时候name变量的值是“艾瑞斯”,而原先的“马里奥”则不再被引用,就应该被当做垃圾回收(“马里奥”变量值被绑定的变量名的个数为0时,该变量值无法被访问到,称之为垃圾,我们在下文会谈到

内存空间的申请与回收都是非常耗费精力的事情,而且存在很大的危险性,稍有不慎就有可能引发内存溢出问题,好在Cpython解释器提供了自动垃圾回收机制来帮我们解决这件事。

>>> 什么是垃圾回收机制?

垃圾回收机制(简称GC)是Python解释器自带,专门用来回收不可用的变量值所占用的内存空间

(2)如何理解GC?GC的底层原理是什么?

>>> 堆区与栈区

在定义变量时,变量名与变量值都是需要存储的,分别对应内存中的两块区域:堆区与栈区。

  • 变量名与值内存地址的关联关系存放于栈区

  • 变量值存放于堆区,内存管理回收的是堆区的内容

为了理解,我们定义两个变量x = 10y = 20,详解如下图

image.png

执行x=y时,内存中的栈区与堆区变化如下:

image.png

>>> 直接引用和间接引用

  • 直接引用:从栈区出发直接引用到的内存地址

  • 间接引用:从栈区出发引用到堆区后,再通过进一步引用才能到达的内存地址

l2 = [20, 30]  # 列表本身被变量名l2直接引用,包含的元素被列表间接引用
x = 10  # 值10被变量名x直接引用
l1 = [x, l2]  # 列表本身被变量名l1直接引用,包含的元素被列表间接引用

图解:

image.png


重点!!!


>>> 垃圾回收机制原理

Python的GC模块主要运用“引用计数”(reference counting)来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”(mark and sweep)解决容器对象可能产生的循环引用的问题,并且通过“分代回收”(generation collection)以空间换取时间的方式来进一步提高垃圾回收的效率。


“引用计数”(reference counting)

引用计数:制的实施变量值被变量名关联的次数

如:

age=18

变量值18被关联了一个变量名age,称之为引用计数为1

引用计数增加:

age=18 (此时,变量值18的引用计数为1)

m=age (把age的内存地址给了m,此时,m,age都关联了18,所以变量值18的引用计数为2)

image.pngimage.png

引用计数减少:

age=10(名字age先与值18解除关联,再与3建立了关联,变量值18的引用计数为1)

del m(del的意思是解除变量名x与变量值18的关联关系,此时,变量18的引用计数为0)

image.png


值18的引用计数一旦变为0,其占用的内存地址就应该被解释器的垃圾回收机制回收

引用计数存在的问题与解决方案

问题一:循环引用

引用计数机制存在着一个致命的弱点,即循环引用(也称交叉引用)

如下:

我们定义了两个列表,简称列表1与列表2,变量名l1指向列表1,变量名l2指向列表2


l1=['xxx']  # 列表1被引用一次,列表1的引用计数变为1   
l2=['yyy']  # 列表2被引用一次,列表2的引用计数变为1   
l1.append(l2) # 把列表2追加到l1中作为第二个元素,列表2的引用计数变为2
l2.append(l1) # 把列表1追加到l2中作为第二个元素,列表1的引用计数变为2

image.png

image.png

如上的变量引用会导致循环引用,此时当我们将变量的数值删除,

del l1 # 列表1的引用计数减1,列表1的引用计数变为1
del l2 # 列表2的引用计数减1,列表2的引用计数变为1

结果会导致值不再被任何名字关联,但是值的引用计数并不会为0,应该被回收但不能被回收,如下图解:

image.png

此时两个列表不再被任何其他对象关联,没有任何人可以再引用到它们,它俩占用内存空间理应被回收,但由于相互引用的存在,每一个对象的引用计数都不为0,因此这些对象所占用的内存永远不会被释放,所以循环引用是致命的,这与手动进行内存管理所产生的内存泄露毫无区别。 所以Python引入了“标记-清除” 与“分代回收”来分别解决引用计数的循环引用与效率低的问题

“标记-清除”

容器对象(比如:list,set,dict,class,instance)都可以包含对其他对象的引用,所以都可能产生循环引用。而“标记-清除”计数就是为了解决循环引用的问题。

标记/清除算法的做法是当应用程序可用的内存空间被耗尽的时,会停止整个程序,然后进行两项工作,第一项则是标记,第二项则是清除

#1、标记
通俗地讲就是:
栈区相当于“根”,凡是从根出发可以访达(直接或间接引用)的,都称之为“有根之人”,有根之人当活,无根之人当死。

具体:标记的过程其实就是,遍历所有的GC Roots对象(栈区中的所有内容或者线程都可以作为GC Roots对象),
    然后将所有GC Roots的对象可以直接或间接访问到的对象标记为存活的对象,其余的均为非存活对象,应该被清除。

#2、清除
清除的过程将遍历堆中所有的对象,将没有标记的对象全部清除掉。


基于上例的循环引用,当我们同时删除l1与l2时,会清理到栈区中l1与l2的内容以及直接引用关系

image.png

如此一来,在启用标记清除算法时,从栈区出发,没有任何一条直接或间接引用可以访达l1与l2,即l1与l2成了“无根之人”,于是l1与l2都没有被标记为存活,二者会被清理掉,这样就解决了循环引用带来的内存泄漏问题。


“分代回收”

作用:基于引用计数的回收机制,每次回收内存,都需要把所有对象的引用计数都遍历一遍,这是非常消耗时间的,于是引入了分代回收来提高回收效率,分代回收采用的是用“空间换时间”的策略。


分代:分代回收的核心思想是:在历经多次扫描的情况下,都没有被回收的变量,gc机制就会认为,该变量是常用变量,gc对其扫描的频率会降低,具体实现原理如下:

分代指的是根据存活时间来为变量划分不同等级(也就是不同的代)

新定义的变量,放到新生代这个等级中,假设每隔1分钟扫描新生代一次,如果发现变量依然被引用,
那么该对象的权重(权重本质就是个整数)加一,当变量的权重大于某个设定得值(假设为3),
会将它移动到更高一级的青春代,青春代的gc扫描的频率低于新生代(扫描时间间隔更长),
假设5分钟扫描青春代一次,这样每次gc需要扫描的变量的总个数就变少了,节省了扫描的总时间,
接下来,青春代中的对象,也会以同样的方式被移动到老年代中。
也就是等级(代)越高,被垃圾回收机制扫描的频率越低


回收:回收依然是使用引用计数作为回收的依据

image.png



添加CDA认证专家【维克多阿涛】,微信号:【cdashijiazhuang】,提供数据分析指导及CDA考试秘籍。已助千人通过CDA数字化人才认证。欢迎交流,共同成长!
41.1764 5 0 关注作者 收藏

评论(0)


暂无数据

推荐课程

推荐帖子