python中的generator(coroutine)浅析和应用

背景知识:

  在Python中一个function要运行起来,它在python VM中需要三个东西。

  1. PyCodeObject,这个保存了函数的代码
  2. PyFunctionObject,这个代表一个虚拟机中的一个函数对象
  3. PyFrameObject,这个代表了函数运行时的调用链和堆栈

   Python正是通过这三样东西模拟0x86的函数调用的

  在python中 coroutine(协程)被称为的generator,这两个东西在python其实是同一个东东,之所以如此称呼是因为它有迭代器的功能,但是又可以只消耗很少的内存。不吃能存,又产生数据,称为generator还是很符合状况的。

  Python中的generotor是一种PyFunctionCode 和PyFrameObject的包装,这个生成器是有自己独立 value stack 的。在加上它能在执行function code的中途返回,并且保存PyFrameObject的状态。所以就有类似线程的一个主要作用了:能够被调度。

  对于操作系统而言,它能够调度的只有线程,而且这种调度发生在内核态,调度时机对于程序员来说是不可知的。一般发生wait某个东西(锁、网络数据、磁盘数据)、时间片用完的时候,这个时候如果是非阻塞的返回,但是当前任务因为缺少数据又不能继续执行,作为要榨干CPU的程序员不能浪费掉分配到时间片,所以应该切换任务。如果一个线程代表一个任务的话,那么在内核就多出一个线程对象。增加内存和调度程序的负担,如果能够在用户态有一种能够由程序员来控制调度的任务,便不用在内核态增加线程对象,任务调度由程序员负责。这个在用户态可以调度的东西就是coroutine了。因为可以被切换,在一个线程内,它应该有自己的堆栈、自己寄存器(状态)-------如果用C/C++这种语言实现的话,如果是在VM中实现,它在发生切换时,只要保持代表当前任务(其实就是函数)状态的PyFrameObject的状态就可以了。



CPython generator涉及的数据结构和对象

1.PyGen_Type

PyTypeObject PyGen_Type = {    PyVarObject_HEAD_INIT(&PyType_Type, 0)
   "generator",                                /* tp_name */
    sizeof(PyGenObject),                        /* tp_basicsize */
    .........省略
    PyObject_GenericGetAttr,                    /* tp_getattro */
   ....... 省略
    (traverseproc)gen_traverse,                 /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    offsetof(PyGenObject, gi_weakreflist),      /* tp_weaklistoffset */
    PyObject_SelfIter,                          /* tp_iter */
    (iternextfunc)gen_iternext,                 /* tp_iternext */
    gen_methods,                                /* tp_methods */
    gen_memberlist,                             /* tp_members */
    gen_getsetlist,                             /* tp_getset */
    .......省略
    gen_del,                                    /* tp_del */
}; 

  从PyGen_Type这个对象对tp_iter,tp_iternext的设置来看,说明generator是实现了iterator protocol了,可以在for 语句中迭代它。

2.PyCodeObject、PyFrameObject,PyFunctionObject

3.PyGenObject

typedef struct {
	PyObject_HEAD
	/* The gi_ prefix is intended to remind of generator-iterator. */
	/* Note: gi_frame can be NULL if the generator is "finished" */
	//PyFrameObject
	struct _frame *gi_frame;

	/* True if generator is being executed. */
	//状态
	int gi_running;
	/* The code object backing the generator */
	//PyCodeObject
	PyObject *gi_code;
	/* List of weak reference. */
	PyObject *gi_weakreflist;
} PyGenObject;


PyGenObject中的gi_running表示状态 0:没有正在运行,1:正在运行,用frame.f_lasti==-1表示没有启动过,因为没有运行过bytecode,所以frame的last instuction offset 会是-1,gi_code对应generator的方法代码,gi_frame为PyFrameObject,用于保存当前generator字节码执行的状态,可以知道generator只能对应一个Frame,它不肯有嵌套的Frame了,也就是不能在generator调用的函数中返回到send/next点,这个对与它的应用来说,会是一个限制,如果业务复杂会导致generator的代码比较臃肿。

CPython 中generator的实现分析:

以这段python代码为分析对象

def gen():
	x=yield 1
	print x
	x=yield 2

g=gen()

g.next()
print g.send("sender")

  对应的Python bytecode为

源码行号 python代码 字节码偏移 字节码 字节码参数 注释
1 def gen(): 0 LOAD_CONST
0 (<code object gen )


这里定义了一个PyFunctionObject,

对应的PyCodeObject

有一个flag(CO_GENERATOR)

标记是一个generator

    3 MAKE_FUNCTION 0  
    6 STORE_NAME 0(gen) gen=PyFunctionObject 
           
7 g=gen() 9 LOAD_NAME 0(gen)   
    12 CALL_FUNCTION  
在PyEval_EvalCodeEX中,因为gen保存的

PyFunctionObject,

对应的PyCodeObject.co_flags

有CO_GENERATOR标记,

它直接返回返回一个PyGenObject

    15 STORE_NAME 1(g)  
           
 9  g.next() 18  LOAD_NAME  1(g)  
     21  LOAD_ATTR  2 (next)
PyObject_GetAttr(g,‘next‘)

PyGen_Type.tp_getattro()

此时tp_getattro=PyObject_GenericGetAttr

得到wrappertype

这个wrapper包含了generator,

     24  CALL_FUNCTION  0

在call 的时候,转而调用 generator.next

就是gen_iternext,之后转到

gen_send_ex这里,

     27  POP_TOP    
           
 10    28  LOAD_NAME  1 (g)  
     31  LOAD_ATTR  3 (send)  
     34  LOAD_CONST  1 (‘sender‘)  
     37  CALL_FUNCTION  1
这里转到

gen_send(PyGenObject *gen,

    PyObject *arg)

     40  PRINT_ITEM    
     41  PRINT_NEWLINE    
     42  LOAD_CONST  2 (None)  
     45  RETURN_VALUE    
           

在分析CPython源码的时候会遇到许多的PyMethodDescrObject、PyMemberDescrObject、PyGetSetDescrObject、PyWrapperDescrObject,是因为Python语言设计的比较灵活,不同的方法、属性,有不同的获取方法,另外不同的方法有不同的参数,所以调用的方式也不一样啊,所以对应的C代码应该有不同的策略,需要包装起到这个策略作用。这些Descr都是一些外层的包装对象,只是为了方便管理而已。在class object初始化的时候保存到相应的type.tp_dict中.

coroutine的应用:

coroutine因为得不到操作系统的主动调用,要有程序员来控制调度时机,在用户态的调度不适合模拟实时的状体,但是非常适合做成无关时间的状态改变,我们以电商快递商品过程的为例,一个商品在卖家到达买家大致会经历下面几个状态:待售、已售、商品在起始城市、商品在中间城市、商品到达目的城市、开始投递、到达买家手中。

快递商品状态转换图

电商商品状态切换伪代码:

from collections import namedtuple

State=namedtuple(‘State‘,‘statename action‘)

def commodity(id):
	#待售状态
	action=yield State(‘forsale‘,‘online‘)

	#已售状体
	if action==‘sellout‘:
		action =yield State(‘sellout‘,‘postman1‘)
	elif action==‘offline‘:
		return

	#在出发城市快递点状态
	if action==‘store1‘:
		action=yield State(‘store1‘,‘store in garage‘)
	else:
		return 

	#已产生中间路径状态
	middleCities=generateRoute(id)
	if action==‘route‘:
		action=yield State(‘store1_routed‘,‘caculate route‘)
	else:
		return

	l=len(middleCities)
	for city in middleCities:
		if action==‘next‘:
			if city==middleCities[l-1]:
				#已经到达目的城市状态
				action =yield State(‘destination‘,city)
			else:
				#中间城市流转状态
				action=yield State(‘middle_city‘,city)

	#在目的城市开始投递状态
	if ‘deliver‘:
		action=yield State(‘delivering‘,‘postman is delivering‘)
	else:
		return
	#被买家接受状态
	if action==‘accept‘:
		yield State(‘accepted‘,‘finish‘)

  

时间: 2024-10-07 11:32:57

python中的generator(coroutine)浅析和应用的相关文章

【Python笔记】如何理解python中的generator functions和yield表达式

本篇笔记记录自己对Python的generator functions和yield表达式的理解. 1. Generator Functions Python支持的generator functions语法允许我们定义一个行为与iterator类似的函数,它可以被用在需要循环调用的场合.与普通函数相比,generator functions只是在函数定义中多了1个yield表达式,除此之外,没有其它特别之处. 当generator函数被创建时,python解释器会自动为它实现iteration p

python中的generator, iterator, iterabel

先来看看如果遇到一个对象,如何判断其是否是这三种类型: 1 from types import GeneratorType 2 from collectiuons import Iterable, Iterator 3 4 isinstance( xx, GeneratorType ) 5 isinstance( xx, Iterable ) 6 isinstance( xx, Iterator ) 生成器对象: 生成器是一个通过yield关键字构建的函数,其返回一个generator对象,同时

python中的生成器(generator)总结

1.实现generator的两种方式 python中的generator保存的是算法,真正需要计算出值的时候才会去往下计算出值.它是一种惰性计算(lazy evaluation). 要创建一个generator有两种方式. 第一种方法:把一个列表生成式的[]改成(),就创建了一个generator: >>> L = [x * x for x in range(10)] >>> L [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] >>

浅析python中_name_=&#39;_main_&#39;

刚接触到python时,对代码中的_name_='_main_'比较疑惑,本文对其的讲解借鉴了其他博客讲述(见参考资料),希望和大家共同学习. Make a script both importable and executable 首先先看一个例子 1 #module.py 2 def main(): 3 print "we are in %s"%__name__ 4 if __name__ == '__main__': 5 main() 在这段函数中,定义main函数,当py文件被

浅析python 中__name__ = &#39;__main__&#39; 的作用

很多新手刚开始学习python的时候经常会看到python 中__name__ = \'__main__\' 这样的代码,可能很多新手一开始学习的时候都比较疑惑,python 中__name__ = '__main__' 的作用,到底干嘛的? 有句话经典的概括了这段代码的意义: "Make a script both importable and executable" 意思就是说让你写的脚本模块既可以导入到别的模块中用,另外该模块自己也可执行. __name__ 是当前模块名,当模块

浅析python中的类变量和对象变量

刚学python,学到了有关于类和对象的地方.对一个概念有点模糊,后来通过实践编码找到一定规律 在python中 class test(object): id=2 name='tt' list=['tt','dd'] def change(self,newA,new_id): self.id=new_id self.age=newA return self.age t1 = test() t1.change(21, 3) print t1.id #3 t2 = test() t2.age = 2

【转】浅析python 中__name__ = &#39;__main__&#39; 的作用

原文链接:http://www.jb51.net/article/51892.htm 举例说明解释的非常清楚,应该是看到的类似博文里面最简单的一篇: 这篇文章主要介绍了python 中__name__ = '__main__' 的作用,对于初学者来说很有帮助,需要的朋友可以参考下 很多新手刚开始学习python的时候经常会看到python 中__name__ = \'__main__\' 这样的代码,可能很多新手一开始学习的时候都比较疑惑,python 中__name__ = '__main__

python中 Lambda,Map,Filter,Itertools,Generator高级函数的用法

Lambda 函数 Lambda 函数是一种比较小的匿名函数--匿名是指它实际上没有函数名. Python 函数通常使用 def a_function_name() 样式来定义,但对于 lambda 函数,我们根本没为它命名.这是因为 lambda 函数的功能是执行某种简单的表达式或运算,而无需完全定义函数. lambda 函数可以使用任意数量的参数,但表达式只能有一个. x = lambda a, b : a * b print(x(5, 6)) # prints '30' x = lambd

浅析Python中的struct模块

最近在学习python网络编程这一块,在写简单的socket通信代码时,遇到了struct这个模块的使用,当时不太清楚这到底有和作用,后来查阅了相关资料大概了解了,在这里做一下简单的总结. 了解c语言的人,一定会知道struct结构体在c语言中的作用,它定义了一种结构,里面包含不同类型的数据(int,char,bool等等),方便对某一结构对象进行处理.而在网络通信当中,大多传递的数据是以二进制流(binary data)存在的.当传递字符串时,不必担心太多的问题,而当传递诸如int.char之