python generator注意事项_python中的generator(coroutine)浅析和应用

背景知识:

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

PyCodeObject,这个保存了函数的代码

PyFunctionObject,这个代表一个虚拟机中的一个函数对象

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 (

这里定义了一个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')

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值