0%

Python

monkey patch

用来在运行时动态修改已有的代码,而不需要修改原始代码。

当我们import一个module时,python会做以下几件事情

  • 导入一个module
  • 将module对象加入到sys.modules,后续对该module的导入将直接从该dict中获得
  • 将module对象加入到globals dict中

当我们引用一个模块时,将会从globals中查找。这里如果要替换掉一个标准模块,我们得做以下两件事情

  1. 将我们自己的module加入到sys.modules中,替换掉原有的模块。如果被替换模块还没加载,那么我们得先对其进行加载,否则第一次加载时,还会加载标准模块。(这里有一个import hook可以用,不过这需要我们自己实现该hook,可能也可以使用该方法hook module import)
  2. 如果被替换模块引用了其他模块,那么我们也需要进行替换,但是这里我们可以修改globals dict,将我们的module加入到globals以hook这些被引用的模块。

重载运算符

Python语言提供了运算符重载功能,增强了语言的灵活性,这一点与C++有点类似又有些不同。鉴于它的特殊性,今天就来讨论一下Python运算符重载。

Python语言本身提供了很多魔法方法,它的运算符重载就是通过重写这些Python内置魔法方法实现的。这些魔法方法都是以双下划线开头和结尾的,类似于__X__的形式,python通过这种特殊的命名方式来拦截操作符,以实现重载。当Python的内置操作运用于类对象时,Python会去搜索并调用对象中指定的方法完成操作。

类可以重载加减运算、打印、函数调用、索引等内置运算,运算符重载使我们的对象的行为与内置对象的一样。Python在调用操作符时会自动调用这样的方法,例如,如果类实现了__add__方法,当类的对象出现在+运算符中时会调用这个方法。

python 重载运算符

环境管理器

高级语法

匿名函数:lambda

高阶函数:map、filter、reduce

迭代器

生成器

枚举

装饰器

面向对象

import实现

当我们执行一行import命令时,Python解释器会查找package这个包的module模块,并将该模块作为mymodule引入到当前的工作空间。所以import语句主要是做了二件事:

  1. 查找相应的module
  2. 加载module到local namespace

查找module的过程

在import的第一个阶段,主要是完成了查找要引入模块的功能,这个查找的过程如下:

1、在缓存 sys.modules 中查找要导入的模块,若找到则直接返回该模块对象

2、如果在 sys.modules 中没有找到相应模块的缓存,则顺序搜索 sys.meta_path,逐个借助其中的 finder 来查找模块,若找到则加载后返回相应模块对象。

3、如果以上步骤都没找到该模块,则执行默认导入。即如果模块在一个包中(如import a.b),则以 a.path 为搜索路径进行查找;如果模块不在一个包中(如import a),则以 sys.path 为搜索路径进行查找。

4、如果都未找到,则抛出 ImportError 异常。

加载

对于搜索到的模块,如果在缓存 sys.modules 中则直接返回模块对象,否则就需要加载模块以创建一个模块对象。加载是对模块的初始化处理,包括以下步骤:

设置属性:包括 namefilepackageloaderpath

编译源码:将模块文件(对于包,则是其对应的 init.py 文件)编译为字节码(*.pyc 或者 *.pyo),如果字节码文件已存在且仍然是最新的,则不重新编译

执行字节码:执行编译生成的字节码(即模块文件或 init.py 文件中的语句)

需要注意的是,加载不只是发生在导入时,还可以发生在 reload 时。

名字绑定

加载完模块后,作为最后一步,import 语句会为 导入的对象 绑定名字,并把这些名字加入到当前的名字空间中。其中,导入的对象 根据导入语句的不同有所差异:

如果导入语句为 import obj,则对象 obj 可以是包或者模块

如果导入语句为 from package import obj,则对象 obj 可以是 package 的子包、package 的属性或者 package 的子模块

如果导入语句为 from module import obj,则对象 obj 只能是 module 的属性

垃圾回收

引用计数机制为主,标记-清除分代收集两种机制为辅的策略

引用计数

Python在内存中存储了每个对象的引用计数(reference count)(每个对象维护一个ob_ref字段)。如果计数值变成0,那么相应的对象就会小时,分配给该对象的内存就会释放出来用作他用。

偶尔也会出现引用循环(reference cycle)。垃圾回收器会定时寻找这个循环,并将其回收。举个例子,假设有两个对象o1和o2,而且符合o1.x == o2和o2.x == o1这两个条件。如果o1和o2没有其他代码引用,那么它们就不应该继续存在。但它们的引用计数都是1。

标记/清除

『标记清除(Mark—Sweep)』算法是一种基于追踪回收(tracing GC)技术实现的垃圾回收算法。它分为两个阶段:第一阶段是标记阶段,GC会把所有的『活动对象』打上标记,第二阶段是把那些没有标记的对象『非活动对象』进行回收。那么GC又是如何判断哪些是活动对象哪些是非活动对象的呢?

对象之间通过引用(指针)连在一起,构成一个有向图,对象构成这个有向图的节点,而引用关系构成这个有向图的边。从根对象(root object)出发,沿着有向边遍历对象,可达的(reachable)对象标记为活动对象,不可达的对象就是要被清除的非活动对象。根对象就是全局变量、调用栈、寄存器。

.svg

在上图中,我们把小黑圈视为全局变量,也就是把它作为root object,从小黑圈出发,对象1可直达,那么它将被标记,对象2、3可间接到达也会被标记,而4和5不可达,那么1、2、3就是活动对象,4和5是非活动对象会被GC回收。

标记清除算法作为Python的辅助垃圾收集技术主要处理的是一些容器对象,比如list、dict、tuple,instance等,因为对于字符串、数值对象是不可能造成循环引用问题。Python使用一个双向链表将这些容器对象组织起来。不过,这种简单粗暴的标记清除算法也有明显的缺点:清除非活动的对象前它必须顺序扫描整个堆内存,哪怕只剩下小部分活动对象也要扫描所有对象。

分代回收

分代回收是一种以空间换时间的操作方式,Python将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python将内存分为了3“代”,分别为年轻代(第0代)、中年代(第1代)、老年代(第2代),他们对应的是3个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为Python的辅助垃圾收集技术处理那些容器对象