您的位置:首页 > 编程语言 > Python开发

Python入门篇(八)之迭代器和生成器

2018-07-05 11:08 513 查看

迭代器和生成器

1、列表生成式

列表生成式即

List Comprehensions
,是
Python
内置的非常简单却强大的可以用来创建
list
的生成式。
举个例子,要生成
list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
可以用
list(range(1, 11))

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

但如果要生成

[1x1, 2x2, 3x3, ..., 10x10]
怎么做?方法一是循环:

>>> L = []`
>>> for x in range(1, 11):
...    L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的

list

>>> [x * x for x in range(1, 11)]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

写列表生成式时,把要生成的元素

x * x
放到前面,后面跟
for
循环,就可以把
list
创建出来,十分有用,多写几次,很快就可以熟悉这种语法。

for循环
后面还可以加上
if判断
,这样我们就可以筛选出仅偶数的平方:

>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

小结:

运用列表生成式,可以快速生成
list
,可以通过一个
list
推导出另一个
list
,而代码却十分简洁。

2、生成器

    通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。

    所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。

在Python中,这种一边循环一边计算的机制,称为生成器:generator。

要创建一个

generator
,有很多种方法。第一种方法很简单,只要把一个列表生成式的
[]
改成
()
,就创建了一个
generator

>> L = [x * x for x in range(10)]
>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>> g = (x * x for x in range(10))
>> g
<generator object &lt;genexpr&gt; at 0x1022ef630&gt;

创建

L
g
的区别仅在于最外层的
[]和()
L
是一个
list
,而
g
是一个
generator

我们可以直接打印出

list
的每一个元素,但我们怎么打印出
generato
r的每一个元素呢?

如果要一个一个打印出来,可以通过

next()
函数获得
generator
的下一个返回值:

&gt;&gt; next(g)
0
&gt;&gt; next(g)
1
&gt;&gt; next(g)
4
&gt;&gt; next(g)
9
&gt;&gt; next(g)
16
&gt;&gt; next(g)
25
&gt;&gt; next(g)
36
&gt;&gt; next(g)
49
&gt;&gt; next(g)
64
&gt;&gt; next(g)
81
&gt;&gt; next(g)
Traceback (most recent call last):
File "&lt;stdin&gt;", line 1, in &lt;module&gt;
StopIteration

从前面我们知道一个概念:

generator
保存的是算法,每次调用
next(g)
,就计算出
g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出
StopIteration
的错误。

当然,上面这种不断调用

next(g)
实在是太变态了,正确的方法是使用
for循环
,因为
generator
也是可迭代对象:

&gt;&gt; g = (x * x for x in range(10))
&gt;&gt; for n in g:
...     print(n)
...
0
1
4
9
16
25
36
49
64
81

所以,我们创建了一个

generator
后,基本上永远不会调用
next()
,而是通过
for循环
来迭代它,并且不需要关心
StopIteration
的错误。

generator
非常强大。如果推算的算法比较复杂,用类似列表生成式的
for
循环无法实现的时候,还可以用函数来实现。

比如,著名的斐波拉契数列

(Fibonacci)
,除第一个和第二个数外,任意一个数都可由前两个数相加得到:

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'

注意,赋值语句:
a, b = b, a + b
相当于:
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

但不必显式写出临时变量t就可以赋值。

上面的函数可以输出斐波那契数列的前N个数:
>>> fib(10)
1
1
2
3
5
8
13
21
34
55
done

仔细观察,可以看出,

fib函数
实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似
generator

也就是说,上面的函数和

generator
仅一步之遥。要把
fib函数
变成
generator
,只需要把
print(b)
改为
yield b
就可以了:

这就是定义

generator
的另一种方法。如果一个函数定义中包含
yield
关键字,那么这个函数就不再是一个普通函数,而是一个
generator

def fib(max):
n,a,b = 0,0,1
while n < max:
#print(b)
yield  b
a,b = b,a+b
n += 1
return "done"

print(fib(6))
f = fib(6)
print(f.__next__())
print(f.__next__())
print(f.__next__())

执行结果:
D:\Python18\venv\Scripts\python.exe D:/Python18/day04/斐波那契数列.py
<generator object fib at 0x0000000000BCB3B8>
1
1
2

断点分析:
(1)定义

fib
函数,传入参数为
max

(2)将
fib(6)
传入
6
作为参数生成一个生成器并赋值给
f
,可以通过
print(fib(6))
查看生成器的内存地址;
(3)
print(f.__next__()
取出生成器的第一个值,此时会直接调用
fib
函数处理;
(4)初始值
n=0、a=0、b=1
,判断
n&lt;6
执行
yield b
保存了函数的中断状态,并返回了
b
的值。此时
print(f.__next__()
执行的结果为
1

(5)再执行
print(f.__next__()
取第二个值,此时会跳回
yield b
,返回函数中断时保存的状态,然后执行
a,b = b,a+b
,此时
a=b,即a=1;b=a+b,即b=1

(6)执行
n += 1
n
的值加
1
,然后回到while循环判断;
(7)判断
1 &lt; 6
,继续执行
yield b
。继续保存函数中断状态,并返回
b
的值。此时返回
print(f.__next__()
的执行结果为
1

(8)同理,再执行第三个
print(f.__next__()
取出第三个值,执行结果为
2
。从而最后的直接结果打印为:
1<br/>1<br/>2

这里,最难理解的就是

generator
和函数的执行流程不一样。
函数是顺序执行
,遇到
return语句
或者最后一行函数语句就
返回
。而变成
generator的函数
,在每次调用
next()
的时候执行,遇到
yield语句返回
,再次执行时从
上次返回的yield语句处继续执行

在上面

fib
的例子,我们在循环过程中不断调用
yield
,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。

同样的,把函数改成

generator
后,我们基本上从来不会用
next()
来获取下一个返回值,而是直接使用
for循环
来迭代:

def fib(max):
n,a,b = 0,0,1
while n < max:
#print(b)
yield  b
a,b = b,a+b
n += 1
return "done"

for n in fib(6):
print(n)

执行结果:
1
1
2
3
5
8

但是用

for循环
调用
generator
时,发现拿不到
generator
return
语句的
返回值
。如果想要拿到返回值,
必须捕获StopIteration错误
,返回值包含在
StopIteration
value
中:

def fib(max):
n,a,b = 0,0,1
while n < max:
#print(b)
yield  b
a,b = b,a+b
n += 1
return "done"
f = fib(6)
while True:
try:
x = next(f)
print(x)
except StopIteration as e:
print('Generator return value:', e.value)
break

执行结果:
f: 1
f: 1
f: 2
f: 3
f: 5
f: 8
Generator return value: done

还可通过

yield
实现在单线程的情况下实现并发运算的效果:以下的执行相当于在串行的过程中实现了并发的效果,也成为协程。

#!/usr/bin/python
# _*_ coding:utf-8 _*_
# Aothr: Kim

import time
def consumer(name):
print("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print("包子[%s]来了,被[%s]吃了!" %(baozi,name))

def producer(name):
c = consumer('A')    #生成一个c的生成器
c2 = consumer('B')   #生成一个c2的生成器
c.__next__()        #执行producer()时,会直接调用到consumer,然后打印A 准备吃包子啦!到了yield直接中断
c2.__next__()       #同上,打印B 准备吃包子啦!
print("老子开始准备做包子啦!")
for i in range(3):    #循环0~2的序列
time.sleep(1)
print("做了2个包子!")
c.send(i)
#此时使用send将i的值发送给yield,继续执行 print("包子[%s]来了,被[%s]吃了!" %(baozi,name)),执行结果为:包子[0]来了,被[A]吃了!while True死循环又到了yield,中断函数。执行下一步。
c2.send(i)   #同上,执行结果为:包子[0]来了,被[B]吃了!至此i=0执行完毕,继续下一个i=1的循环。

producer("alex")

执行结果:
A 准备吃包子啦!
B 准备吃包子啦!
老子开始准备做包子啦!
做了2个包子!
包子[0]来了,被[A]吃了!
包子[0]来了,被[B]吃了!
做了2个包子!
包子[1]来了,被[A]吃了!
包子[1]来了,被[B]吃了!
做了2个包子!
包子[2]来了,被[A]吃了!
包子[2]来了,被[B]吃了!

3、迭代器

一类是集合数据类型,如

list、tuple、dict、set、str
等;

一类是

generator
,包括生成器和带
yield
generator function

这些可以

直接
作用于
for循环的对象
统称为可迭代对象:
Iterable

可以使用

isinstance()
判断一个对象是否是
Iterable
对象:

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

而生成器不但可以作用于

for循环
,还可以被
next()函数
不断调用并返回下一个值,直到最后抛出
StopIteration
错误表示无法继续返回下一个值了。

可以被

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

可以使用

isinstance()
判断一个对象是否是
Iterator
对象:

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是

Iterator
对象,但
list、dict、str
虽然是
Iterable
,却不是
Iterator

list、dict、str
Iterable
变成
Iterator
可以使用
iter()
函数:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

你可能会问,为什么

list、dict、str
等数据类型不是
Iterator

这是因为

Python
Iterator
对象表示的是一个数据流,
Iterator
对象可以被
next()
函数调用并不断返回下一个数据,直到没有数据时抛出
StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过
next()
函数实现按需计算下一个数据,所以
Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用
list
是永远不可能存储全体自然数的。

小结
凡是可作用于

for循环
的对象都是
Iterable类型

凡是可

作用于next()函数
的对象都是
Iterator
类型,它们表示一个惰性计算的序列;

集合数据类型如

list、dict、str
等是
Iterable
但不是
Iterator
,不过可以通过
iter()函数
获得一个
Iterator
对象。

Python
for循环
本质上就是通过
不断调用next()函数
实现的,例如:

for x in range(5):
print(x)

实际上完全等价于:

# 首先获得Iterator对象:
it = iter([0,1, 2, 3, 4])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Python 生成器 迭代器