Python-02进阶-03生成器


title: Python进阶知识03-生成器.md
tags: 2019年 08月 11号
notebook: 00技术笔记

Python生成器

列表/字典推导式

列表推导式 样例

# >> 实现列表自加一

info = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# 方法1 enumerate循环
for index,i in enumerate(info):
    info[index] +=1
# 方法2 map方法
a = map(lambda x:x+1,info)
# 方法3 列表推导式
a = [i+1 for i in range(10)]

In [3]: print a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

字典推导式 样例

In [5]: {k:k+1 for k in info}
Out[5]: {0: 1, 1: 2, 2: 3, 3: 4, 4: 5, 5: 6, 6: 7, 7: 8, 8: 9, 9: 10}

生成器

  • 生成器是一个特殊的程序,可以被用作控制循环的迭代行为.
  • python中生成器是迭代器的一种,使用yield返回值函数,每次调用yield会暂停,
  • 而可以使用next()函数和send()函数恢复生成器。
  • 生成器可以节省大量内存。

生成器表达式

把一个列表生成式的[]中括号改为()小括号,就创建一个generator

#列表生成式
lis = [x*x for x in range(10)]
print(lis)
#生成器
generator_ex = (x*x for x in range(10))
print(generator_ex)

结果:
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr> at 0x000002A4CBF9EBA0>

生成器表达式使用
使用next()获取生成器的下一个返回值

generator_ex = (x*x for x in range(4))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
print(next(generator_ex))
结果:
0
1
4
9
# 超出值时会抛出异常
Traceback (most recent call last):
  File "列表生成式.py", line 42, in <module>
    print(next(generator_ex))
StopIteration

创建生成器后,一般使用for循环实现。

生成器函数

使用yield 构造生成器函数
yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。
而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行

任何使用了yield的函数就是生成器,生成器就是一个返回迭代器的函数,或者说生成器就是一个迭代器。

def fib(max):
    n,a,b =0,0,1
    while n < max:
        yield b
        a,b =b,a+b
        n = n+1

a = fib(10)
print next(a)
for i in a:
    print i

小结

生成器的用法

  • next() 返回生成器下个值
  • close() 关闭生成器。生成器被关闭后,再次调用next()方法,不管能否遇到yield关键字,都会立即抛出StopIteration异常。
  • send() 可以通过send()方法,向生成器内部传递参数.继续运行yield之后的代码。
  • throw() 除了向生成器函数内部传递参数,还可以传递异常。

生成器用法样例

#! -*- coding:utf-8 -*-
def iterator_func(val):
   for i in range(val):
       print("生成器值:%s"%i)
       tmp = yield i
       if tmp:
           print("send传递值:%s"%tmp)
num = 5
gen = iterator_func(num)
# 必须先next()调用,开始生成器
gen.next()
for i in range(10):
    print(">>>>>>>> ")
    gen.send(i + 10)
    if i >= num:
        # throw 方法自定义异常
        gen.throw(Exception,u"自定义异常:数值不够")
    if i >= 1:
        # close()后. 到下一个next或send直接抛出异常
        gen.close()

生成器值:0
>>>>>>>> 
send传递值:10
生成器值:1
>>>>>>>> 
send传递值:11
生成器值:2
>>>>>>>> 
Traceback (most recent call last):
  File "a", line 14, in <module>
    gen.send(i+10)
StopIteration

生成器的分类

  • 生成器函数: 也是用def定义的,利用关键字yield一次性返回一个结果,阻塞,重新开始
  • 生成器表达式: 返回一个对象,这个对象只有在需要的时候才产生结果

生成器的优点

  • 节省内存。大量数据时尤为明显。
  • 节省代码,减少代码量同时提高代码可读性。
  • 模拟并发。

模拟并发。Python虽然支持多线程,但是由于GIL(全局解释锁,Global Interpreter Lock)的存在,同一个时间,只能有一个线程在运行,所以无法实现真正的并发。这时就出现了协程。复杂解释不说了,简单说协程就是你可以暂停执行的函数"。也就是yield。

Python实现协程最简单的方法,就是使用yield。当一个函数在执行过程中被阻塞时,就用yield挂起,然后执行另一个函数。当阻塞结束后,可以用next()或者send()唤醒。相比多线程,协程的好处是它在一个线程内执行,避免线程之间切换带来的额外开销,而且协程不存在加锁的步骤。

迭代器

迭代器包含有next方法的实现,在正确的范围内返回期待的数据以及超出范围后能够抛出StopIteration的错误停止迭代。

Iterable可迭代对象

使用isinstance()判断一个对象是否为可Iterable对象

# Iterable 可迭代对象
In [1]: from collections import Iterable

In [2]: isinstance([], Iterable)
Out[2]: True

In [3]: isinstance((x for x in range(10)), Iterable)
Out[3]: True

In [4]: isinstance('test', Iterable)
Out[4]: True

In [5]: isinstance(123, Iterable)
Out[5]: False

Iterator迭代器

一个实现了iter方法的对象是可迭代的,一个实现next方法并且是可迭代的对象是迭代器。

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

所以一个实现了iter方法和next方法的对象就是迭代器。

In [6]: from collections import Iterator

In [7]: isinstance((x for x in range(10)), Iterator)
Out[7]: True

In [8]: isinstance([], Iterator)
Out[8]: False

迭代器和可迭代对象之间的转换

Iterable转为Iterator: iter([1,2,3])
Iterator转为Iterable: list((x+1 for x in range(10)))

In [11]: isinstance(iter([]), Iterator)
Out[11]: True

In [12]: isinstance(iter('abc'), Iterator)
Out[12]: True

In [13]: isinstance([], Iterator)
Out[13]: False

In [15]: isinstance(list((x for x in range(10))),Iterator)
Out[15]: False

总结

  • 凡是可作用于for循环的对象都是Iterable类型;
  • 凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
  • 集合数据类型如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。

对yield的总结
  (1)通常的for…in…循环中,in后面是一个数组,这个数组就是一个可迭代对象,类似的还有链表,字符串,文件。他可以是a = [1,2,3],也可以是a = [x*x for x in range(3)]。

它的缺点也很明显,就是所有数据都在内存里面,如果有海量的数据,将会非常耗内存。

(2)生成器是可以迭代的,但是只可以读取它一次。因为用的时候才生成,比如a = (x*x for x in range(3))。!!!注意这里是小括号而不是方括号。

(3)生成器(generator)能够迭代的关键是他有next()方法,工作原理就是通过重复调用next()方法,直到捕获一个异常。

(4)带有yield的函数不再是一个普通的函数,而是一个生成器generator,可用于迭代

(5)yield是一个类似return 的关键字,迭代一次遇到yield的时候就返回yield后面或者右面的值。而且下一次迭代的时候,从上一次迭代遇到的yield后面的代码开始执行

(6)yield就是return返回的一个值,并且记住这个返回的位置。下一次迭代就从这个位置开始。

(7)带有yield的函数不仅仅是只用于for循环,而且可用于某个函数的参数,只要这个函数的参数也允许迭代参数。

(8)send()和next()的区别就在于send可传递参数给yield表达式,这时候传递的参数就会作为yield表达式的值,而yield的参数是返回给调用者的值,也就是说send可以强行修改上一个yield表达式值。

(9)send()和next()都有返回值,他们的返回值是当前迭代遇到的yield的时候,yield后面表达式的值,其实就是当前迭代yield后面的参数。

(10)第一次调用时候必须先next()或send(),否则会报错,send后之所以为None是因为这时候没有上一个yield,所以也可以认为next()等同于send(None)

yield实现单线程并发

yield单线程并发样例

#! -*- coding:utf-8 -*-
import time

def consumer(name):
    print('%s 准备学习了~' %(name))
    while True:
        lesson = yield
        print('开始[%s]了,[%s]老师来讲课了~' %(lesson,name))

def producer(name):
    c1 = consumer('A')
    c2 = consumer('B')
    c1.next() # 先调用c1使后面的send能够传值
    c2.next() # 先调用c2使后面的send能够传值
    print('同学们开始上课了~')
    for i in range(3):
        time.sleep(1)
        print('到了两个同学')
        c1.send(i)
        c2.send(i)

producer('westos')

# > 返回结果
A 准备学习了~
B 准备学习了~
同学们开始上课了~
到了两个同学
开始[0],[A]老师来讲课了~
开始[0],[B]老师来讲课了~
到了两个同学
开始[1],[A]老师来讲课了~
开始[1],[B]老师来讲课了~
到了两个同学
开始[2],[A]老师来讲课了~
开始[2],[B]老师来讲课了~

"""
利用了关键字yield一次性返回一个结果,阻塞,重新开始
send 唤醒
"""

第三方函数库-greenlet

"""
使用greenlet完成多任务
为了更好的使用协程来完成多任务,python中的greeblet模块
对其进行的封装
"""
from greenlet import greenlet
import time

def test1():
    while True:
        print('---A----')
        gr2.switch()
        time.sleep(0.5)

def test2():
    while True:
        print('----B----')
        gr1.switch()
        time.sleep(0.5)

"""
greenlet这个类对yield进行的封装
"""
gr1= greenlet(test1)
gr2 = greenlet(test2)
# 相当于开关,开启后两个函数之间能够相互切换执行
gr1.switch()

参考资源

生成器使用
python(生成式、生成器)

原文地址:https://www.cnblogs.com/superscfan/p/12256983.html