进阶
杂知识点
什么是 CPython GIL?
GIL,Global Interpreter Lock,即 全局解释器锁
引入 GIL 是因为 CPython 的内存管理并 不是线程安全 的
为了保护多线程下对 python 对象的访问,每个线程在执行过程中都需要先获取 GIL,保证同一时刻只有一个线程在执行代码
GIL 使得 python 的多线程不能充分发挥多核 CPU 的性能,对 CPU 密集型程序的影响较大
另一种解释
全局解释 器锁 GIL,英文名称为 Global Interpreter Lock,它是解释器中一种线程同步的方式。
对于每一个解释器进程都具有一个 GIL ,它的直接作用是限制单个解释器进程中多线程的并行执行,使得即使在多核处理器上对于单个解释器进程来说,在同一时刻运行的线程仅限一个。 对于 Python 来讲,GIL 并不是它语言本身的特性,而 是 CPython 解释器的实现特性。
Python 代码被编译后的字节码会在解释器中执行,在执行过程中,存在于 CPython 解释器中的 GIL 会致使在同一时刻只有 一个线程 可以执行字节码。 GIL 的存在引起的最直接的问题便是:在一个解释器进程中通过多线程的方式无法利用多核处理器来实现真正的并行。
因此,Python 的多线程是 伪多线程,无法利用多核资源,同一个时刻只有一个线程在真正的运行。
Python 的内存管理
Python 有 内存池机制,Pymalloc 机制,用于对内存的申请和释放管理。
先来看一下为什么有内存池:
- 当创建大量消耗小内存的对象时,c 中频繁调用 new/malloc 会导致大量的内存碎片,致使效率降低。
- 内存池的概念就是预先在内存中申请一定数量的,大小相等的内存块留作备用,当有新的内存需求时,就先从内存池中分配内存给这个需求,不够了之后再申请新的内存。这样做最显著的优势就是能够 减少内存碎片,提升效率。
查看源码,可以看到 Pymalloc 对于小的对象,Pymalloc 会在内存池中申请空间 ,一般是少于 236kb,如果是大的对象,则直接调用 new/malloc 来申请新的内存空间。
python 垃圾回收机制
引用计数 为主,标记清除 和 分代回收 为辅
引用计数
引用计数机制是这样的:
- 当对象被创建,被引用,作为参数传递,存储到容器中,引用计数 +1
- 当对象离开作用域,引用指向别的对象,del,从容器中移除,引用计数 -1
- 当引用计数降为 0,python 就会自动回收该对象所在的内存空间,
但是引用计数 无法解决循环引用的问题,所以引入了标记清除和分代回收机制
标记清除
标记清除(Mark-And-Sweep)主要是解决循环引用问题。
标记清除算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。
它分为两个阶段:第一阶段是标记阶段,GC 会把所有的 活动对象 打上标记,第二阶段是把那些没有标记的对象 非活动对象 进行回收。那么 GC 又是如何判断哪些是活动对象哪些是非活动对象的呢?
对象之间通过引用(指针)连在一起,构成一个 有向图,对象构成这个有向图的节点,而引用 关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。
分代回收
分代回收是一种以空间换时间的操作方式。
Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3 “代”,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们对应的是 3 个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python 垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。
async 和 await 的作用
async: 声明一个函数为异步函数,函数内只要有 await 就要声明为 async
await: 搭配 asyncio.sleep() 时会切换协程,当切换回来后再继续执行下面的语句
OOP 相关
类变量和实例变量的区别?
- 类变量由所有实例共享,一个对象对其进行修改,其他对象也都会被修改
- 实例变量由实例单独享有,不同实例之间不影响
- 当我们需要在一个类的不同实例之间共享变量的时候使用类变量
classmethod 和 staticmethod 区别?
- 都可以通过
Class.method()的方式使用 - classmethod 第一个参数是 cls,可以引用类变量
- staticmethod 使用起来和普通函数一样,不需要用到类变量,只不过放在类里去组织而已
- classmethod 是 为了使用类变量,staticmethod 是代码组织的需要,完全可以放到类之外
class Person:
Country = 'china'
def __init__(self, name, age):
self.name = name
self.age = age
def print_name(self):
print(self.name)
@classmethod
def print_country(cls):
print(cls.Country)
@staticmethod
def join_name(first_name, last_name):
return print(last_name + first_name)
a = Person("Bruce", "Lee")
a.print_country()
a.print_name()
a.join_name("Bruce", "Lee")
Person.print_country()
Person.print_name(a)
Person.join_name("Bruce", "Lee")
__new__ 和 __init__ 区别?
__new__是一个静态方法,而__init__是一个实例方法.__new__方法会 返回一个创建的实例,而__init__什么都不返回.- 只有在
__new__返回一个 cls 的实例时后面的__init__才能被 调用. - 当创建一个新实例时调用
__new__,初始化一个实例时用__init__.
我们可以做几个有趣的实验。
class Person:
def __new__(cls, *args, **kwargs):
print("in __new__")
instance = super().__new__(cls)
return instance
def __init__(self, name, age):
print("in __init__")
self._name = name
self._age = age
p = Person("zhiyu", 26)
print("p:", p)
这段程序输出为:
in __new__
in __init__
p: <__main__.Person object at 0x00000261FE562E50>
可以看到先执行 new 方法创建对象,然后 init 进行初始化。假设将 new 方法中不返还该对象,会有什么结果了?
class Person:
def __new__(cls, *args, **kwargs):
print("in __new__")
instance = super().__new__(cls)
# return instance
def __init__(self, name, age):
print("in __init__")
self._name = name
self._age = age
p = Person("zhiyu", 26)
print("p:", p)
发现 如果 new 没有返回实例化对象,init 就没法初始化了。
输出结果为:
in __new__
p: None
什么是元类?
元类 (meta class) 是创建类的类
元类允许我们控制类的生成,比如修改类的属性等
使用 type 来定义元类
元类最常见的一个使用场景就是 ORM 框架
functools.lru_cache() 装饰器
该函数是一个装饰器,为函数提供缓存功能。在下次 以相同参数调用 时直接返回上一次的结果。
例 1:生成第 n 个斐波纳契数,这种慢速递归函数适合使用 lru_cache
import time
def fibonacci(n):
"""斐波那契函数"""
if n < 2:
return n
return fibonacci(n - 2) + fibonacci(n - 1)
if __name__ == '__main__':
stime = time.time()
print(fibonacci(34)) # 没有使用缓存,则需要几秒钟的时间
print("total time is %.3fs" % (time.time() - stime))
# output
# 5702887
# total time is 1.335s
如果没有使用缓存,则需要几秒钟的时间,像下面这样使用缓存后,瞬间就可以计算出结果。
import datetime
import functools
@functools.lru_cache(maxsize=300)
def fibonacci(n):
"""斐波那契函数"""
if n < 2:
return n
return fibonacci(n - 2) + fibonacci(n - 1)