Python 细节问题
Python 细节问题
1、 连接字符串用join还是+
当用操作符+连接字符串的时候,每执行一次+都会申请一块新的内存,然后复制上一个+操作的结果和本次操作的右操作符到这块内存空间,因此用+连接字符串的时候会涉及好几次内存申请和复制。而join在连接字符串的时候,会先计算需要多大的内存存放结果,然后一次性申请所需内存并将字符串复制过去,这是为什么join的性能优于+的原因。所以在连接字符串数组的时候,应考虑优先使用join。
2、Python 垃圾回收机制
参考 https://blog.csdn.net/xiongchengluo1129/article/details/80462651
Python中的垃圾回收是以引用计数为主,分代收集为辅。引用计数的缺陷是循环引用的问题。
在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存。
引用计数法的原理是每个对象维护一个ob_refcnt,用来记录当前对象被引用的次数,也就是来追踪到底有多少引用指向了这个对象,当对象被创建、对象被引用、对象被传入函数、被存储在容器中等四种情况时,该对象的引用计数器 +1
对象被创建 a=14
对象被引用 b=a
对象被作为参数,传到函数中 func(a)
对象作为一个元素,存储在容器中 List={a,”a”,”b”,2}
与上述情况相对应,当发生对象别名被 del 销毁时、对象的引用被赋予新对象时、汉书执行完毕后、从容器中删除时等四种情况,该对象的引用计数器-1
当该对象的别名被显式销毁时 del a
当该对象的引别名被赋予新的对象, a=26
一个对象离开它的作用域,例如 func函数执行完毕时,函数里面的局部变量的引用计数器就会 -1(但是全局变量不会)。
将该元素从容器中删除时,或者容器被销毁时。
当指向该对象的内存的引用计数器为0的时候,该内存将会被Python虚拟机释放.
sys.getrefcount(a)可以查看 a 对象的引用计数,但是比正常计数大1,因为调用函数的时候传入a,这会让 a 的引用计数+1
引用计数的优点:
1、gao效
2、运行期没有停顿:一旦没有引用,内存就直接释放了。不用像其他机制等到特定时机。实时性还带来一个好处:处理回收内存的时间分摊到了平时。
3、对象有确定的生命周期
4、易于实现
引用计数的缺点:
1、维护引用计数消耗资源,维护引用计数的次数和引用赋值成正比,而不像mark and sweep等基本与回收的内存数量有关。
2、无法解决循环引用的问题。A和B相互引用而再没有外部引用A与B中的任何一个,它们的引用计数都为1,但显然应该被回收。
# 循环引用示例 list1 = [] list2 = [] list1.append(list2) list2.append(list1)
为了解决这两个缺点 Python 还引入了另外的机制:标记清除和分代回收.
标记清除
『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:*一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?
对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。
标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。
Python使用一个双向链表将这些容器对象组织起来。*,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它*顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。
分代回收
分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象。
GC 的逻辑
分配内存 -> 发现超过阈值了 -> 触发垃圾回收 -> 将所有可收集对象链表放到一起 -> 遍历, 计算有效引用计数 -> 分成 有效引用计数=0 和 有效引用计数 > 0 两个集合 -> 大于0的, 放入到更老一代 -> =0的, 执行回收 -> 回收遍历容器内的各个元素, 减掉对应元素引用计数(破掉循环引用) -> 执行-1的逻辑, 若发现对象引用计数=0, 触发内存回收 -> python底层内存管理机制回收内存
Python 中, 一个代就是一个链表, 所有属于同一”代”的内存块都链接在同一个链表中用来表示“代”的结构体是 gc_generation, 包括了当前代链表表头、对象数量上限、当前对象数量。
Python默认定义了三代对象集合,索引数越大,对象存活时间越长,新生成的对象会被加入第0代,前面_PyObject_GC_Malloc中省略的部分就是Python GC触发的时机。每新生成一个对象都会检查第0代有没有满,如果满了就开始着手进行垃圾回收。
分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间较久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。
3、递归
Python 递归深度默认是多少?递归深度限制的原因是什么?
Python 递归深度可以用内置函数库中的 sys.getrecursionlimit() 查看。
因为无限递归会导致的 C 堆栈溢出和 Python 崩溃。
更多培训课程,学习资讯,课程优惠等学校信息,请进入 武汉江岸区Web培训武汉江汉区Python培训武汉硚口区Linux云计算培训 网站详细了解,免费咨询电话:400-998-6158