python-05-01-列表解析_生成器_迭代器

  • 目录
  • 列表解析
  • 生成器
  • 迭代器

列表解析

什么是列表解析?
定义: 基于一个列表生成另外一个列表,或动态生成一个列表。
如:

1
2
3
4
5
In [26]: [ i ** 2 for i in range(2,8)]    # 对原列表乘以2
Out[26]: [4, 9, 16, 25, 36, 49]

In [25]: [ i for i in range(1,10) if i % 2 ] # 对原列表取奇数
Out[25]: [1, 3, 5, 7, 9]

练习1: 有如下文件

1
2
3
# cat word.txt
render practice inventory indent accelerate,
trigger status,wxq.

我们计算word.txt单词个数

1
2
3
4
5
6
7
8
9
10
11
In [29]: f = open('/root/word.txt', 'r')
In [31]: word_list = [ word for line in f for word in line.split() ]

In [36]: print(word_list)
['render', 'practice', 'inventory', 'indent', 'accelerate,', 'trigger', 'status,wxq.']

In [37]: f.seek(0) # 上面读取过文件,指针己然在最后了,这里是把指针移到文件首部
Out[37]: 0

In [38]: len([ word for line in f for word in line.split() ]) # 计算结果为7
Out[38]: 7

我们计算word.txt非空白字符数

1
2
3
4
5
In [37]: f.seek(0)       # 把指针移到文件首部
Out[37]: 0

In [41]: sum([ len(word) for line in f for word in line.split() ])
Out[41]: 58

练习2: 打印一个3行5列的矩阵

1
2
3
In [45]: list1 = [ (x+1, y+1) for x in range(3) for y in range(5) ]
In [47]: print(list1)
[(1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (2, 1), (2, 2), (2, 3), (2, 4), (2, 5), (3, 1), (3, 2), (3, 3), (3, 4), (3, 5)]

练习3:

1
2
3
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.items()]
['y=B', 'x=A', 'z=C']

练习4:

1
2
3
4
In [61]: L = ['I', 'WILL', 'STUDY', 'HARD', 'PYTHON']

In [62]: [ s.capitalize() for s in L ]
Out[62]: ['I', 'Will', 'Study', 'Hard', 'Python']

生成器

列表解析会创建完整列表占用内存,那么有没有一种算法或机制让我们循环使用时推算出后续元素,这样节省了内存空间。
定义:这种边循环边计算的机制,称为生成器generator

创建一个生成器,其中一个方法就是把前面的列表解析[] 换成()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
In [107]: g = (i for i in range(4))

In [108]: g
Out[108]: <generator object <genexpr> at 0x7f98a27ba150>

In [117]: g.send(None)
Out[117]: 0

In [118]: g.send(None)
Out[118]: 1

In [119]: next(g)
Out[119]: 2

In [120]: next(g)
Out[120]: 3

In [122]: next(g)
---------------------------------------------------------------------------
StopIteration Traceback (most recent call last)
<ipython-input-122-5f315c5de15b> in <module>()
----> 1 next(g)

StopIteration:

利用yield创建一个生成器,取出完整元素需要用for 循环来迭代

1
2
3
4
5
6
7
8
9
10
11
In [123]: def genNum(x):
.....: i = 0
.....: while i < x:
.....: yield i
.....: i += 1

In [124]: g = genNum(5)
In [136]: for i in g:
.....: print(i, end=' ')
.....:
0 1 2 3 4

来看一下yield工作细节

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
In [138]: def consumer():
.....: print("yield starting")
.....: m = yield 5
.....: print("m_values: %s" % m)
.....: n = yield 12
.....: print("Game over")
.....:

In [139]: c = consumer()

In [140]: c.send(None)
yield starting
Out[140]: 5

In [141]: c.send('to be brave')
m_values: to be brave
Out[141]: 12

小结:

  • c.send(None)遇到第一个yield停止,在解释器中out yield中的值,在python yield.py却不会.
  • c.send(‘to be brave’)会上次中止处继续,’to be brave’会赋值给yield 5,即m = ‘to be brave’, 遇到第二个yield停止.
  • g.send(None)启动迭代器,遇到第一个yield返回.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    root@apt:~/script_study# cat yield.py 
    #!/usr/bin/env python3
    #
    def h():
    print("yield starting")
    m = yield 5
    print("m_values: %s" % m)
    d = yield 12
    print('Game over')
    c = h()
    # print test info
    c.send(None)
    c.send('Fighting!')

    root@apt:~/script_study# python3 yield.py
    yield starting
    m_values: Fighting!

后面协程正是用的这个知识点。

迭代器

细心的朋友不难发现生成器可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值为止。

  • 可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator
  • 可以直接作用于for循环的对象统称为可迭代对象:Iterable

哪些是可迭代对象呢?或说可作用于for循环呢?有以下几种数据类型:

一类是集合数据类型,如list、tuple、dict、set、str等;

一类是generator,包括生成器和带yield的generator function。

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

1
2
3
4
5
6
7
8
9
10
11
>>> 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

可以使用isinstance()判断一个对象是否是Iterator对象:

1
2
3
4
5
6
7
8
9
>>> 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()函数:

1
2
3
4
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

为什么list、dict、str等数据类型不是Iterator 呢?

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

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

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

for x in [1, 2, 3, 4, 5]:
pass
实际上完全等价于:

1
2
3
4
5
6
7
8
9
10
# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
try:
# 获得下一个值:
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break

总结

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

迭代器:能够作用于next()函数,不断向后返回值,称为迭代器 iter([1,2,3,4])
生成器:yield返回,并且可以next(),是一种特殊的迭代器
可迭代对象:一个类中有__iter__()方法,并返回迭代器(包括生成器),那么这个类的对象就是可迭代对象,可for循环