自学Python4.8-装饰器 1

自学Python之路-Python基础+模块+面向对象
自学Python之路-Python网络编程
自学Python之路-Python并发编程+数据库+前端
自学Python之路-django

自学Python4.7 - 生成器

定义:生成器(generator)是一个包含yield关键字的函数,当它被调用的时候,在函数体中的代码不会被执行,而是会返回一个迭代器。
          (一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator);        如果函数中包含yield语法,那这个函数就会变成生成器;)

  • 生成器是一个特殊的程序,可以被用作控制循环的迭代行为
  • 生成器类似于返回值为数组的一个函数,这个函数可以接收参数,可以被调用,但是,不同于一般的函数会一次性返回包含了所有数值的数组,生成器一次 只产生一个值,这样消耗的内粗数量大大减少,而且允许调用函数可以很快的开始处理前几个返回值。因此,生成器看起来像一个函数但是表现的却像一个迭代器

1. python提供了两种基本的生成器方式:

  • 生成器函数:也是用def来定义,利用关键字yield一次返回一个结果,阻塞,重新开始
                         每次请求一个值,就会执行生成器中的代码,知道遇到一个yield或者return语句
                         ①yield语句意味着应该生成一个值
                         ②return语句意味着要停止执行(不生成任何东西,只有在一个生成器中使用时才能进行无参数调用)
  • 协程生成器表达式:返回一个对象,这个对象只有在需要的时候才产生结果

2. 生成器函数:  

      为什么叫生成器函数?因为他随着时间的推移生成了一个数值队列。一般的函数在执行完毕之后会返回一个值然后退出,但是生成器函数会自动挂起,然后重新拾起继续执行,他会利用yield关键字关起函数,给调用者返回一个值,同时保留了当前的足够多的状态,可以使函数继续执行。
     生成器和迭代协议是密切相关的,可迭代的对象都有一个__next()__成员方法,这个方法要么返回迭代的下一项,要么引起异常结束迭代。
     为了支持迭代协议,拥有yield语句的函数被编译为生成器,这类函数被调用时返回一个生成器对象,返回的对象支持迭代接口,即成员方法__next()__继续从中断处执行执行。

def func():   #func是函数称为生成器,当执行此函数func()时会得到一个迭代器。
    yield 1
    yield 2
    yield 3
    yield 4
temp = func()
print(temp.__next__())
print(temp.__next__())
print(temp.__next__())
print(temp.__next__())

输出
1
2
3
4

def creat_counter(n):
    print('create counter')
    while True:
        yield n
        print('increment n')
        n += 1
cnt = creat_counter(2)
print(cnt)
print(next(cnt))
print(next(cnt))
print(next(cnt))

输出

<generator object creat_counter at 0x00547E10>
create counter
2
increment n
3
increment n
4
分析以上的列子:

  • 在create_counter函数中出现了关键字yield,预示着这个函数每次只产生一个结果值,这个函数返回一个生成器(通过第一行输出可以看出来),用来产生连续的n值
  • 在创造生成器实例的时候,只需要像普通函数一样调用就可以,但是这个调用却不会执行这个函数,这个可以通过输出看出来
  • next()函数将生成器对象作为自己的参数,在第一次调用的时候,他执行了create_counter()函数到yield语句,返回产生的值2
  • 我们重复的调用next()函数,每次他都会从上次被挂起的地方开始执行,直到再次遇到了yield关键字
def cube(n):
    for i in range(n):
        yield i ** 3
for i in cube(5):
    print(i)

输出:

0
1
8
27
64
从理解函数的角度出发我们可以将yield类比为return,但是功能确实完全不同,在for循环中,会自动遵循迭代规则,每次调用next()函数,所以上面的结果不难理解。

举例2:

举例1:

举例3: 取不到值会报错

 

举例4: 使用for循环取值

 

举例5.1  : 打印出10个哇哈哈

 

举例5.2 :如果现在有10个哇哈哈, 我现在只想用5个

迭代器依旧延续向下走:

3. 协程生成器表达式:

 生成器表达式来自于迭代和列表解析的组合,生成器表达式和列表解析类似,但是他使用尖括号而不是方括号括起来的。

print([ x ** 3 for x in range(5)]) # 列表解析生成列表
print((x ** 3 for x in range(5))) # 生成器表达式
print(list(x ** 3 for x in range(5)))# 两者之间转换

输出:

[0, 1, 8, 27, 64]
<generator object <genexpr> at 0x01D97E10>
[0, 1, 8, 27, 64]

for n in (x ** 3 for x in range(5)):
    print('%s, %s' % (n, n * n))  # 就操作而言,生成器表如果使用大量的next()函数会显得十分不方便,for循环会自动出发next函数  

输出:

0, 0
1, 1
8, 64
27, 729
64, 4096


4. 生成器函数与协程生成器表达式比较

  一个迭代既可以被写成生成器函数,也可以被协程生成器表达式,均支持自动和手动迭代。而且这些生成器只支持一个active迭代,也就是说生成器的迭代器就是生成器本身。

def recv():
    print('Are your ready:')
    while True:
        n = yield  #yield语句还有更给力的功能,作为一个语句出现在赋值运算符的右边,接受一个值,或同时生成一个值并接受一个值
        print('总共用了 %s 秒' % n)
c = recv()
c.__next__()
c.send(100)
c.send(300)

输出:

Are your ready:
总共用了 100 秒
总共用了 300 秒
解释:以上这种方式使用yield语句的函数称为协程。在这个例子中,对于__next__的初始调用是必不可少的,这样协程才能执行可通向第一个yield表 达式的语句。在这里协程会挂起,等待相关生成器对象send()方法给它发送一个值。传递给send()的值由协程中的yield表达式返回。 协程的运行一般是无限期的,使用方法close()可以显式的关闭它。

def split_line():
    print('ready to split')
    result = None
    while True:
        line = yield result  #如果yield表达式中提供了值,协程可以使用yield语句同时接收和发出返回值
        result = line.split()
s = split_line()
s.__next__()
print(s.send('1,2,3'))

输出:

ready to split
['1,2,3']
解释:
这个例子中的先后顺序非常重要。首个next()方法让协程执行到yield result,这将返回result的值None。
在接下来的send()调用中,接收到的值被放到line中并拆分到result中。
send()方法 的返回值就是下一条yield语句的值。也就是说,send()方法可以将一个值传递给yield表达式,但是其返回值来自下一个yield表达式,而不是接收send()传递的值的yield表达式。

③  如果想用send()方法来开启协程的执行,必须先send一个None值,因为这时候是没有yield语句来接受值的,否则就会抛出异常

def split_line():
print('ready to split')
result = None
while True:
line = yield result #如果yield表达式中提供了值,协程可以使用yield语句同时接收和发出返回值
result = line.split()
s=split_line()
print(s.send('1 2 3'))

输出:

def split_line():
    print('ready to split')
    result = None
    while True:
        line = yield result  #如果yield表达式中提供了值,协程可以使用yield语句同时接收和发出返回值
        result = line.split()
s=split_line()
print(s.send(None))
print(s.send('1 2 3'))

输出:

ready to split
None
['1', '2', '3']

④ 生成器的功能非常强大。协程可以用于实现某种形式的并发。在某些类型的应用程序中,可以用一个任务调度器和一些生成器或协程实现协作式用户空 间多线程,即greenlet。

yield的威力将在协程,协同式多任务处理(cooperative multitasking),以及异步IO中得到真正的体现。

.

猜你喜欢

转载自www.cnblogs.com/yaoyaojcy/p/10575960.html