基础知识
使用函数名称调用函数(方法)
eval
eval直接将函数字符串名称转换成函数对象
locals globals
locals和globals以字典形式分别返回当前位置的全部局部变量和全局变量,这就包括了定义的方法
1 | |
1 | |
getattr
当通过对象调用函数的时候,可以使用getattr
methodcaller
也可以使用operator模块的methodcaller调用函数
1 | |
__init__ 和 __new__
__init__是初始化方法,创建对象后,就立刻被默认调用了,可接收参数。__init__中的self参数就是__new__返回的对象
__new__必须要有返回值,返回实例化出来的实例
is 和 ==
is是检查对象的标示符是否一致,==是检查俩个对象是否相等,a is b就是id(a) == id(b),a == b就是a.__eq__(b)
内存驻留
交互式命令行,对于只包含字母、数字、下划线组成的字符串,python会自动保留一个副本,当创建新的字符串的时候就直接指向该副本,此外的字符串不会驻留在内存中,
1 | |
可变对象和不可变对象
可变对象在创建后可改变但是地址不会改变,不可变对象创建后不能改变。字典和列表是可变对象,int、字符串、元组和float是不可变对象
join和+
在连接列表中的字符串的时候,join会优先计算需要多大的内存,一次性申请,使用+就会一步一步申请,性能比前者差。
finally
当try语句块中含有break、continue、return的时候,在离开try之前,finally的语句会被执行
enumerate
1 | |
zip()
1 | |
collections
collections中包含除了集合、元素、列表、字典意外的数据类型
1 | |
1 | |
单例模式
使用模块
python的模块天然就是一个单例,在一个模块中生成一个对象,在其他模块中导入这个对象全部都是同一个对象。因为python在导入模块的时候会生成pyc文件,在导入的从这个pyc文件导入对象
使用装饰器
1 | |
使用类
在__new__方法中实现
1 | |
使用元类
1 | |
协程
协程通过中断实现,在执行一个协程的时候,可以随时中断,去执行另一个协程;协程是在一个线程中执行,没有线程开换的开销,不需要多线程的锁机制,不存在写变量冲突,控制共享资源不加锁。
python中使用生成器实现对协程的支持
1 | |
常用库函数
os
1 | |
装饰器
用类去做装饰器
可以用于非参数化装饰器
1 | |
参数化装饰器
1 | |
保留内省
以上的装饰器会让原函数时区注释文档和函数名称,使用functools可解决问题
1 | |
使用场景
- 检查参数
- 缓存:将输出保存在某个变量中,在一定时间内,如果再输入一样的参数,那么输出直接从变量中查询
- 代理:比如flask使用装饰器做路由分发,django使用装饰器做账号认证
- 上下文
类装饰器
调用对象自己就是调用call方法
1 | |
迭代器
实现了迭代器协议的容器对象
- next 返回容器下一个元素
- iter 返回迭代器本身
1 | |
yield
使用next获取yield的值
def fibonacci():
a,b= 0,1
while True:
yield b
a,b = b,z + b
fib = fibonacci()
next(fib)
next(fib)
next(fib)
使用send向yield发送参数
def psy():
print("okok")
while True:
answer = (yield)
if answer is not None:
if answer.endswith('?'):
print("dont ask more")
elif 'good' in answer:
print("nonono")
else:
print("whatever")
p = psy()
next(p)
p.send("hehe?")
p.send("not that good")
p.send("jkl")
super多重继承的问题
在多重继承中,在初始化的时候容易产生问题
1 | |
最佳实践
- 避免多继承
- super使用必须一致,要么全部用super,要么全不用
- 显示的继承object,为了避免代码在python2中混用新式类和旧式类
- 调用父类的时候查看层次结构,使用__mro__
描述符
- __set__(self,obj,type=None)
- __get__(self,obj,value)
- __delete__(self,obj)
实现了__get__()和__get__()的描述符是数据描述符,如果只实现了__get__()那就是非数据描述符
在每次查找对象的属性的时候,__getattribute__(),会按以下顺序查找属性
- 该属性是否是实例的类对象的数据描述符
- 如果不是,那就查看该属性能否在实例的
__dict__中找到 - 查看该属性是否是实例的类对象的非数据描述符
python已经使用描述符将类函数绑定为实例方法
延迟求值属性
class InitOnAccess(object):
def __init__(self,klass,*args,**kwargs):
self.klass = klass
self.args = args
self.kwargs = kwargs
self._initialized = None
def __get__(self,instance,owner):
if self._initialized is None:
print('initialized!')
self._initialized = self.klass(*self.args,**self.kwargs)
else:
print('cached')
return self._initialized
class MyClass():
lazy_bone = InitOnAccess(list,"argument")
m = MyClass()
m.lazy_bone
m.lazy_bone
property
将属性连接到方法上
class Rectangle(object):
def __init__(self,x1,y1,x2,y2):
self.x1,self.x2,self.y1,self.y2 = x1,x2,y1,y2
@property
def width(self):
return self.x2 - self.x1
@width.setter
def width(self,value):
self.x2 = self.x1 + value
rec = Rectangle(1,5,2,6)
print(rec.width)
rec.width = 10
print(rec.width)
__getattr__
当获取一个对象的某个不存在的属性的时候,就会调用这个对象的这个方法
__getattribute__
对象的所有属性都会调用这个方法,如果自定义修改的话,比较容易出现无限循环。注意在这个函数里面调用属性的时候使用super
__slots__
会限制类的实例中可以添加的属性,但是实际上这个是用来优化类的属性存储的,一旦设置了这个属性,解释器就会使用类似命名元组的存储结构进行存储类的属性,而不是使用字典去存储
解释器会忽略继承的__slots__属性
如果不把__weakref__加入__slots__中,该实例就不能作为弱引用
元类
python中一切皆对象,是对象就可以在运行时进行修改。如下图

任何一个实例是一个对象,是对象就是某一个类实例化而生成的;类也是对象,那么也是某一个类实例化生成的,最终是type的实例化,
类都有父类,最终的父类是object类。
可以使用type生成一个实例(类),
klass = type('MyClass',(object,),{'method':lambda x:print(1)})
k = klass()
k.method() # 1
自定元类的参数与type一致,并且返回另一个类对象即可
1 | |
元类的模板如下
class MetaClass(type):
# 创建具体的类对象
def __new__(mcs,name,bases,namespace):
return super().__new__(mcs,name,bases,namespace)
# 创建namespace对象
@classmethod
def __prepare__(mcs,name,bases,**kwargs):
return super().__prepare__(name,bases,**kwargs)
# 初始化,此时已经生成类对象cls
def __init__(cls,name,bases,namespace,**kwargs):
super().__init__(name,bases,namespace)
# 覆写类实例创建和初始化的默认方式
def __call__(cls,*args,**kwargs):
return super().__call__(*args,**kwargs)
实际编写元类的如下
class RevealingClass(metaclass=RevealingMeta):
def __new__(cls):
return super().__new__(cls)
def __init__(self):
super().__init__()
# 对应元类的初始化过程 __prepare__ --> __new__ --> __init__
内置方法和实现的关系
1 | |
列表推导
在某些情况下可以替换filter、map
1 | |
可变对象
- 在函数的参数设置默认值是可变对象,比如是列表,那么这个函数的所有调用都会使用这一个可变对象,而不是在每次调用的时候新建一个
- 在使用*进行列表和元组生成的时候,当列表或者元组里面的元素是可变对象的时候,复制后的列表和元组里面是指向同一个对象的
可散列对象
一个可散列对象,在其生命周期内,散列值是不变的,这个对象实现了__hash__和__eq__方法,元组是不可变对象,但是当元组内部存在可变对象元素的时候,这个元组就不是可散列对象
字典推导
1 | |
不可变映射类型
在python3.3之后,在types模块中有MappingProxyType,这个类会返回一个只读的映射视图
from types import MappingProxyType
di = {1:"D"}
di_proxy = MappingProxyType(di)
di_proxy[2] = "C" #将会报异常
集合
集合中的元素是可散列的,但是set本身是不可散列的,frozenset是可以散列的。
集合中自带的中缀运算符
he = set([1,2,3])
ha = set([2,3,4,5,6])
he & ha
he | ha
ha - he
ha in he
ha <= he
he < ha
he >= ha
he > ha
创建集合的时候,使用一下俩种方式,一般俩说前者比后者要更高效,因为针对前者,解释器会利用BUILD_SET字节码直接创建集合,而后者是利用构造函数创建
1 | |
集合推导
1 | |
字典中的散列表
散列表是一个稀疏数组,散列表里的单元叫做表元,在字典的散列表中,一个键值对占用一个表元,所有表元的大小相同。
如果俩个对象的比较(==)的时候,那么这俩个对象的散列值也要相同,
在my_dict[key]中,解释器会先计算hash(key),根据值的后几位去查找表元,如果没找到,就报KeyError,如果不为空,就会在表元里面找到found_key:found_value,这个时候解释器就会检验key==found_key,如果为真,就会返回found_value。
如果key和found_key和key不匹配的话,这种情况情况就是散列冲突。原因是散列值的后几位相同。这时解释器就会在散列值中另外取几位,处理过后得到的数字当做索引,寻找新的表元。
- 键必须是可散列的。
- 用户自定义对象默认是可散列的,散列值由id获取。
- 字典在内存上开销较大。
- 如果需要需要存放数量巨大的记录,那么最好存放在元组或者具名元组构成的列表
-
键查询很快
-
键的次序取决于添加的顺序
-
向字典里面添加新的键,可能会改变已有键的顺序
- 不要在迭代一个字典的时候修改一个字典
字符和字节
把码位转换成字节序列的叫编码,把字节序列转换成码位的过程是解码
二进制不可变bytes和可变bytearray,二者的元素都是位于0-255之间的整数。
1 | |
处理文本文件
在python3中,默认的open函数读取文件的时候自动做必要的解码,在写入文件的时候做必要的编码,python2需要使用io.open()
reduce
在python3中,reduce被放到functools模块中,而内置的sum函数就可以取代reduce的求和功能
all和any
1 | |
仅限关键字参数
在python3中,定义函数时若想指定仅限关键字参数,要把它们放到前面有*的参数后面。如果不想支持数量不定的定位参数,但是想支持仅限关键字参数,在签名中放一个*
def hello(a,*,b=1):
print(a)
__defaults__ 定位参数的默认值
__kwdefaults__ 仅限关键字参数的默认值
__code__.co_varnames 参数名称,不包括变长参数
__code__.co_argcount 参数个数,不包括变长参数
from inspect import signature
sig = signature(hello)
print(str(sig)) #获取函数hello的参数部分完整的形式
print(sig.parameters) #这是一个有序字典(OrderedDict)
函数注解
def hello(name:str,where:'int > 0' = 10) -> str:
return "hello world"
注解只会存储在__annotations__中,解释器不会做任何其他事情
函数式编程
函数式编程关心数据的映射,命令式编程关心解决问题的步骤
函数式编程具有“函数式一等公民”的特性
operator
#包括很多常用运算符的函数
from operator import mul
#itemgetter(1) 相当于 lambda x:x[1],本身就是可迭代对象中的一个元素,itemgetter(1,0)就相当于lambda x:(x[1],x[0])
from operator import itemgetter
for c in sorted(data,key=itemgetter(1)):
print(c)
#attrgetter(name) 相当于 lambda x:getattr(x,name),attrgetter(name,age) 相当于 lambda x:(getattr(x,name),getattr(x,age))
from operator import attrgetter
info = attrgetter('name','age')
class Student(object):
name = "hehe"
age = 17
stu = Student()
print(info(stu))
from operator import methodcaller
myupper = methodcaller('upper')
myupper("haha") # 相当于 "haha".upper()
变量作用域
b=3
def hello():
print(b)
b = 1
# 该函数执行时会报错,因为b在函数中定义,解释器就会把b视为局部变量,在打印b的时候就会去找局部变量b,而此时b还没有定义,所以报错
闭包
函数访问在定义体之外定义的非全局变量

avg = make_averager() #执行过后,make_averager的局部作用域已经消失,对averager的来说,自由变量series已经被绑定
avg(10) # 10
avg(11) # 10.5
avg(12) # 11
print(avg.__code__.co_freevars) # (series,)
print(avg.__closure__[0].cell_centents)
nonlocal
对于在闭包中给不可变对象赋值,会隐式地将自有变量更改为局部变量,从而导致异常
def hehe():
count = 0
total = 0
def average(new):
count += 1 # count是不可变对象,重新赋值相当于默认定义了这个变量,但是实际没有在average内部定义count,执行到这里就会报错
total += new
return total/count
return average
def hehe():
count = 0
total = 0
def average(new):
nonlocal count,total #把变量编程自有变量,每次有更新的时候就会更新绑定,python2没有这个关键字,只能通过转换成可变对象进行操作
count += 1
total += new
return total/count
return average
弱引用
del
del不是删除对象,而是删除对象的引用
weakref
python控制台会有_绑定结果不为None的表达式的值
import weakref
a = {1,2}
b = weakref.ref(a)
b()
a = {3,4}
b()
b() is None # Flase
b() is None # True
WeakValueDictionary
是可变类型的映射,值是对象的弱引用,当被引用法人对象被删除后,对应的对象的键就会被删除
list和dict不能作为弱引用的目标,但是它们的子类可以;int和tuple不能作为弱引用的目标,其子类也不可以
classmethod staticmethod
classmethod用于装饰类专属的方法,装饰的函数的第一个参数都是指代类,staticmethod就是普通的函数,只是写在了类里面。
格式化
print('1 dao = {money:0.2f}'.format(money=qian))
print(format(42,"b")) #用二进制显示数字42
对自定义类使用format,如果没有定义__format__,那么久使用继承自object中的__format__,就是str(),但是这种情况无法使用格式说明
基本的序列协议
实现__len__和__getitem__,从而支持索引、遍历、in操作
实现了__setitem__机会将序列变成可变序列,从而实现一些就地操作,比如就地打乱
内置类型子类
内置类型的方法不会调用子类覆盖的方法

但是__missing__方法可以正常运行
结论:不要直接继承内置的类,如果有需要,继承collections中的UserDict、UserList、UserString,内置类型dict,str,list都是使用C实现的
可迭代
iter(x)
-
如果对象实现了
__iter__函数,那就调用它,获取一个迭代器 -
如果没有,但是实现了
__getitem__方法,解释器会创建一个迭代器,利用这个方法将元素放进去 -
如果创建失败,那么抛出异常
可迭代对象
-
实现了
__iter__ -
实现了
__getitem__
Python从可迭代对象中获取迭代器。
标准的迭代器接口有俩个方法:
-
__next__:返回迭代器下一个元素,如果没有,抛出StopIteration -
__iter__:返回self
可迭代对象不能拥有__next__方法,必须拥有__iter__方法;迭代器可以一直迭代,__iter__方法应该返回自身
iter(func,tag) # 返回一个可调用的迭代器,在迭代的时候,会一直运行func,知道func的返回值是tag,并且不输出tag
1 | |
生成器
函数中使用yield,该函数就叫生成器函数,生成的对象就是生成器对象
生成器表达式
列表推导式会一次性生成一个列表,生成器表达式
def gen():
print("1")
yield 'A'
print("2")
yield 'B'
print("3")
listQ = [x for x in gen()] # 直接生成一个列表
genQ = ( x for x in gen()) # 生成一个生成器,只在需要的时候运行生成器
上下文管理器和with
上下文管理器包括__enter__和__exit__俩个方法,with语句开始运行时,会调用上下文管理器对象的__enter__方法,运行结束后,会调用__exit__方法
with open("text.txt") as fp: #执行with 后面的表达式得到的结果是上下文管理器对象,不过,把值绑定到目标变量上(as 子句)是在上下文管理器对象上调用 __enter__ 方法的结果
fp.read()
class LookingClass:
def __enter__(self):
import sys
self.origin_write = sys.stdout.write
sys.stdout.write = self.reverse_write
return 'HEHE'
def reverse_write(self,text):
self.origin_write(text[::-1])
def __exit__(self,exc_type,exc_value,traceback): #异常类,异常实例,traceback对象
import sys
sys.stdout.write = self.origin_write
if exc_type is ZeroDivisionError:
print("No")
return True
contextlib
@contextmanager
通过编写一个类,并且实现__enter__和__exit__来编写上下文管理器相对比较麻烦,@contextmanager装饰器可以优化这个问题
import contextlib
@contextlib.contextmanager
def looking_glass():
import sys
origin_write = sys.stdout.write
def reverse_write(text):
origin_write(text[::-1])
sys.stdout.write = reverse_write
try:
yield "ASDFG" # with语句会在这里暂停
except Exception:
pass
finally:
sys.stdout.write = origin_write # 出了with语句,就会执行这句话
with looking_glass() as what:
print("HAHA") # AHAH
print(what) # GFDSA
协程
yield

def averager():
total = 0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total/count
v = averager()
next(v) # 预激协程
v.send(10) # 10
v.send(12) # 11
v.send(14) # 12
当协程遇到异常会向上抛出,如果上层没有处理,那么就会导致协程退出
可以在生成器调用throw和close方法停止协程
def handle():
while True:
try:
x = yield
except ZeroDivisionError:
print("okok")
else:
print("x is ",x)
h = handle()
next(handle)
handle.send(1)
handle.throw(ZeroDivisionError) #通过调用throw,向协程抛出异常,由于协程有处理,所以可以继续运行
handle.throw(Exception) # 协程终止
from collections import namedtuple
Result = namedtuple('Result', 'count average')
def averager():
total = 0.0
count = 0
average = None
while True:
term = yield
if term is None:
break
total += term
count += 1
average = total/count
return Result(count, average)
a = averager()
next(a)
a.send(10)
a.send(20)
try:
a.send(None)
except StopIteration as exc:
result = exec.value
yield from
def chain(*iterable):
for it in iterable:
yield from it
s = 'abc'
t = tuple(range(3))
print(list(chain(s,t))) # ['A', 'B', 'C', 0, 1, 2]
yield from x 会调用iter(x)
yield from 的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。

对于RESULT = yield from EXPR,可以简单理解成如下
1 | |
元编程
所有类的继承关系的始祖都是Object,所有类型关系的始祖都是type
1 | |
PyCodeObject
python源码编译后,生成一个pyc文件,存放着PyCodeObject对象
编译的时候,对于代码中的一个代码块,会创建一个PyCodeObject对象与这个段代码对应,当进入一个新的命名空间,或者说作用域,就算是新的代码块
命名空间是符号的上下文环境,符号的含义取决于命名空间
1 | |
在python代码中,PyCodeObject对象可以通过__code__属性查看,以下是在交互命令行中查看
1 | |
pyc生成
主要包括三个部分
- magic number
- 魔数,用于和解释器自己的魔数比较,避免版本不同导致字节码指令集不兼容而无法正常运行程序
- pyc文件创建时间
- 可以用于判定是否需要重新编译出pyc
- PyCodeObject对象
- 先写入类型,比如TYPE_CODE,TYPE_INT,
- 对数值的写入,将数值转换成16进制
- 写入字符串,这个比较麻烦。。。
字节码
python2.5一共有104条字节码指令
官方提供dis模块,可以解析字节码
1 | |
最左侧一列是字节码指令对应源码的位置,第二列是字节码在co_code中的偏移位置,第3列是字节码,最后一列显示字节码参数
虚拟机框架
字节码中没有执行环境,这里的执行环境包括命名空间,以及一些其他信息,虚拟机实际执行的是PyFrameObject。
PyFrameObject结构
1 | |
可以使用如下方法获取当前活跃的PyFrameObject对象
1 | |
赋值语句就是建立约束的地方,约束的容身之所就是命名空间,命名空间是用PyDictObject实现的。
一个module内部,可能有多个命名空间,每个命名空间都和一个作用域相对应,作用域仅由源程序的文本决定。
最内嵌套作用域规则: LEGB:Local、Enclosing、Global、Builtin
PyEal_EvalFramEx
遍历字节码指令序列:
- first_instr:字节码指令序列开始
- f_lasti:上一条已经执行的字节码在co_code中的所以
- next_instr:指向下一条待执行的字节码指令位置
python中的一个线程就是操作系统上的原生线程
- 一个线程有一个PyThreadState对象,
- PyInterpreterState是python中进程的抽象
多线程机制
全局解释器锁GIL
- 时钟中断,即执行了多少个指令后,进行线程线程调度,
sys.getcheckinterval(),默认是100
thread模块
- start_new_thread对应
thread_PyThread_start_new_thread- 初始化bootstate结构boot,保存线程一切信息
- 初始化Python多线程环境
- 创建GIL,结构体是NRMUTEX
- owned
- thread_id
- hevent
- 在win32环境下,hevent就是Win32的Event对象
- 在获取GIL时,通过参数waitflag,0,python会检查GIL是否可用,通过owned值,如果为-1,表示可以获得
- 当释放GIL时,会通知所有等待中的进程,至于唤醒哪个进程,就在操作系统自己的实现
- 创建GIL,结构体是NRMUTEX
- 以boot为参数,创建操作系统原生线程
内存管理
内存分配
小块空间的内存池
block
pool
默认大小为系统的内存页,一般时4KB。一个pool管理着许多大小相同的block
struct pool_header {
union {
block *_padding;
uint count;
}ref;
block *freeblock;
struct pool_header *nextpool;
struct pool_header *prevpool;
uint arenaindex;
uint szidx;
uint nextoffset;
uint maxnextoffset;
}
在pool内,block可以连续申请,当其中一个block被释放的时候,为了避免内存不连续,将这些被释放的block按顺序组成链表,freeblock就是这个链表的头
arena
pool的集合就是arena,默认大小时256KB,即64个pool
struct arena_object{
uptr address;
block* pool_address;
uint nfreepools;
uint ntotalpools;
struct pool_header* freepools;
struct arena_object* nextarena;
struct arena_object* prearena;
}
多个arena构成数组,数组的首地址由arenas维护。
当arena没有与pool建立联系的时候,这时的arena处于未使用的状态,一旦建立了联系,这时的arena就是可用状态
usedpools
默认的大小块内存的边界SMALL_REQUEST_THRESHOLD为256字节,当申请的内存小于256字节时,PyObject_Malloc会在内存池中申请,当大于时,就malloc
pool状态
- used 所有used pool都被usedpools管控
- full
- empty 所有的empty pool都被freepools管控
垃圾收集
主要是引用计数,标记清除和分代收集是为了打破循环引用的补充计数
可收集对象链表
被垃圾收集机制监控的container对象
- PyGC_HEAD
- PyObject_HEAD
- Container object
其中PyGC_Head,除了包含可收集对象链表的前后指针外,还包含gc_ref
分代收集
在python中,有三代,实际是维护了三个链表,_PyGC_generation0、_PyGC_generation1、_PyGC_generation2
所有新创建的对戏都会被加入第0代链表中,第0代内存链表有700容量,一旦超过700,就会触发内存回收
当对第1代进行回收前,会将第0代的链表merge到第1代后面,对第2代回收也是如此
寻找root object集合
有效引用计数,即将循环引用去除,A引用了B,B引用了A,那么A、B的游戏引用计数就是0,gc_ref就是这个值
垃圾收集的第一步,就是遍历可收集对象链表,设置gc_ref,然后遍历container对象中的每一个引用,将这些引用对象的gc_ref减1,遍历完整个列表后,gc_ref不为0的对象,就是rootobject
然后,从rootobject出发,沿着引用链,一步一步标记不能回收的内存,最终,形成俩个一条reachable链表和一条unreachable链表,
对于unreachable链表,其中的对象,如果有__del__方法,那么就将其移到garbage的PyLiastObject对象中
使用C/C++开发模块
//添加头文件
#include <Python.h>
#include <stdio.h>
//编写对应的功能函数
void fly(const char *name)
{
printf("%s is flying.\n", name);
}
//添加python调用版本,在其中调用编写的功能函数
static PyObject *bird_fly(PyObject *self, PyObject *args)
{
const char *name;
if (!PyArg_ParseTuple(args, "s", &name))//把python对象的args转换为C对象name,s表示是string转换成char*,其他还有:i int->int,z string or None -> char*, O :object -> PyObject*,还可以解析字典元组之类的,{s:i,s:i} :dict -> char*,int.char*,int
return NULL;
fly(name);//执行功能函数
//这里是返回值,因为功能函数没有返回值,所以返回Py_None
//如果是有返回值,那么需要使用Py_BuildValue将C对象转换成python对象,其中的格式和PyArg_ParseTuple一样
//return (PyObject*)Py_BuildValue("i",1);
Py_INCREF(Py_None);//显示地增加Py_None对象的引用计数
return Py_None;
}
//添加向Python呈现C函数的方法表bird_methods
static PyMethodDef bird_methods[] = {
{ "fly", bird_fly, METH_VARARGS, "Bird fly" },//分别是python解释器调用的函数名,实际执行的函数体,函数签名类型(一般是METH_VARARGS即元组的形式传参,无参数时METH_NOARGS,也可以METH_KEYWORD,即使用python字典形式传参),函数描述
{ NULL, NULL, 0, NULL }
};
//添加模块初始化函数initbird,必须以init模块名 这样,这里使用的python2版本的初始化方式
PyMODINIT_FUNC initbird(void)
{
PyImport_AddModule("bird");
Py_InitModule("bird", bird_methods);
}
//python3使用如下形式
/*
static struct PyModuleDef BirdModule = {
PyModuleDef_HEAD_INIT,//默认
"bird",//模块名
NULL,
-1,
bird_methods //上面的数组
};
void PyInit_bird(void){
PyModule_Create(&BirdModule);
}
*/
多线程和多进程
threading
threading模块封装了很多的同步原语,
- Lock
- 原始锁,实际上就是使用匿名信号量实现的,一般语言里面的原始锁可以由互斥量、临界区、信号量实现的,python这里选择了匿名信号量实现,也就是说,这个锁,谁(所有线程)都可以减去获取锁acquire、就是信号量减一,释放锁release、信号量加一
- Rlock
- 重入锁RLock,相当于java的重入锁,基于Lock实现的,就是在acquire的时候记录当前线程id,release的时候判定线程是不是acquire的线程
- condition,条件,维护一个锁Lock/RLock,和一个waiting池,线程通过acquire获得condition对象,当wait的时候,线程会释放condition内部的锁进入block状态,同时waiting池中记录这个线程,当notify,condition对象从waiting池中挑选一个线程,通知其acquire
- Semaphore,信号量,基于Condition实现
- BoundedSemaphore,防止Semaphore被无限释放
- event 基于Condition,控制线程间运行顺序
- Barrier 基于Condition,让特定线程先运行
- Timer 基于Thread和Event实现,定时任务
从锁上看,python原生库提供的支持很基础,而且没有特定的优化,相对于java在锁方面,大量运用CAS原语,提供各种字节码指令,优化各种并发情况下的同步操作,以达到高性能,python在这方面就很随意,为什么呢?因为python本身有着GIL,在多线程效率方面就天生有着限制,我觉得GIL就是对单核操作系统执行多进程的模拟,另外,如果说高并发的话,python可以用协程实现,比如tornado、asyncio,底层使用是IO多路复用、select/poll/epoll/kqueue等,不过老实说,在协程方面,python又不如go、scala、erlang等完善,python自己的协程框架asyncio的三方生态太少,单说高并发,也不及Go等
创建线程调用链: start -> _thread._start_new_thread -> thread_Pythread_start_new_thread -> PyThread_start_new_thread -> pthread_create
通过这个调用链看出,在linux,python的线程实现,就是通过pthread库函数创建的,是一种用户态的线程,和linux内核进程一对一
线程池
不管是线程池还是进程池,都要维护俩个核心东西:进(线)程队列和任务队列,进(线)程队列中的进程从任务队列中取任务去执行,为了取任务的时候,各个进(线)程不冲突,需要一个锁,并将结果返回给一个对象,一般都叫future对象。