python 迭代器,生成器与列表推导式
迭代器
可以迭代的数据类型:元组、列表、字典、集合、字符串
判断是否可迭代:
1 | from collections import Iterable |
判断是否是迭代器:
1 | from collections import Iterable |
print 的两种书写形式:
1 | print('number is %d' % 25) |
创建出来的可迭代的实例对象:
普通的类
添加
__iter__
,使其具有可迭代属性,即:isinstance 返回 True1
2def __iter__(self):
pass__iter__
方法中返回一个具有__iter__
方法以及__next__
对象的引用
一个对象是否可以用 for 来迭代遍历(电脑程序的判断方法):
根据对象中有无
__iter__
方法,即:使用 isinstance() 判断在第一步成立的前提下,使用 iter 方法
iter(classmate)
来调用该对象的__iter__
方法,获取其返回值,也就是迭代器对象的引用使用返回值,即:迭代器,调用其自身的 next 方法
next(classmate_iter)
来获取其__next__
方法,获取其返回值注:称一个同时具有
__iter__
和__next__
方法的类成为迭代器。每一次 for 循环通过迭代器中的
__next__
方法来取其返回值
让 __next__
方法输出可迭代对象的东西,需要再将可迭代对象在 __iter__
方法创建迭代器时,传入自己的 self 引用。此外,在迭代器中写一个
__init__
接受引用。
在迭代器中设置一个属性,用来标志当前的序号,然后通过比较序号,如果当前序号小于列表的长度,那么接着取,否则会默认返回一个 None 值。
通过使用 raise StopIteration
引发此异常,来使 for
循停止迭代。
源码:
1 | from collections import Iterable |
下面将可迭代对象与迭代器合成一个对象(也就是迭代器一定可迭代,但是可迭代的对象不一定是迭代器):
1 | from collections import Iterable |
迭代器的优点:
存储指定生成的数据的方式,而不是结果。如果当结果过多时,存储起来会花费大量的空间。
例如:python 中的 range 与 xrange
使用迭代器写斐波那契数列:
1 | import sys |
并不是只有 for 循环能接收迭代器,list(), tuple() 等也能接收
1 | li = list(fina(8)) |
这并不是简单的类型转换,而是通过迭代器逐个转换。比如:
1 | In [29]: a=(11,22,33) |
首先创建一个空列表,然后找到 a 的迭代器,使用 next 函数找其
__next__
方法的返回值逐个迭代转换,最后遇到迭代停止异常,进而停止。
列表生成式与生成器表达式
列表生成式:保存生成结果,将结果存到变量。 使用方括号
生成器表达式:保存生成结果的方式,for 循环迭代的时候,逐个计算输出。 使用圆括号
1 | In [31]: num = [x*2 for x in range(10) if x%2 == 0] |
生成器
利用迭代器,我们可以在每次迭代获取数据(通过 next 方法)时,按照特定的规律进行生成。但是,我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合 next() 函数进行迭代使用,我么可以采用更简便的语法:生成器(generator),生成器是一类特殊的迭代器。
创建生成器的方法
将列表生成式的
[]
改为()
在普通函数中加上 yield(只要函数里面有 yield,无论是在 if, for 等等里面,这个函数就不是函数了,变成了生成器。)
如果一个函数中有 yield 语句,那么它就不再是一个函数,而是一个生成器。
如果在调用函数时,发现这个函数中有 yield, 那么此时不再调用函数,而是创建一个生成器对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17def create_num(all_num):
a, b =0, 1
current_num = 0
while current_num < all_num:
yield a
a, b = b, a+b
current_num += 1
obj = create_num(12)
# print(next(obj))
# print(next(obj))
for num in obj:
print(num)
for 循环触发迭代器:
第一次:程序从头开始运行,然后运行到 yield,yield 语句会暂停程序,将 yield 后面的值返回给 for 循环的变量,然后开始进行 for 循环。下一次 for 循环,进入生成器后,从 yield 语句的下一条语句开始执行。
通过 next 触发迭代器,开始迭代。
注意:不同的生成器对象有不同的局部变量。互不干扰。
如果想要通过 while True 调用 next 方法来取值,可以通过捕获 StopIteration 异常来结束。
1 | while True: |
如何接受迭代器的返回值?
可以在其执行完之后,通过异常对象
except StopIteration as e:
或者
except Exception as e:
中 e 的 value 属性来打印。
1 | while True: |
小结
- 使用了 yield 关键字的函数不再是函数,而是生成器。(使用了 yield 的函数就是生成器)
- yield 关键字有两点作用:
- 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
- 将 yield 关键字后面表达式的值作为返回值返回,此时可以理解为起到了 return 的作用
- 可以使用 next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
- Python3 中的生成器可以使用 return 返回最终运行的返回值,而 Python2 中的生成器不允许使用 return 返回一个返回值(即可以使用 return 从生成器中退出,但 return 后不能有任何表达式)
使用 send 唤醒 我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用 send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。即:使用 send 唤醒 yield a 的同时,将消息传递给 yield a 的结果。即:temp = yield a, temp 就是当使用 send 唤醒时,传入的结果。
例子:执行到 yield 时,gen 函数作用暂时保存,返回 i 的值; temp 接收下次 c.send("python"),send 发送过来的值,c.next()等价 c.send(None)
1 | In [10]: def gen(): |
注意:
- send() 不能作为第一次唤醒使用,因为第一次从生成器的开始执行,而开始往往不是 yield a, 这样就没有东西接受 send 传入的值,因此会抛出异常。
- 如果必须第一次使用 send, 那传入消息 None。大多情况下使用 next。
- next 是函数,直接使用
next(obj)
, 而 send 是迭代器对象的方法,需要使用:obj.send('message')
- 具有 yield 的函数是生成器对象,需要先创建对象,然后才可以使用。
迭代器和生成器都保存生成数据的方法,而不是数据,节省空间。
迭代器减少内存空间,生成循环。生成器可以让一个函数看上去暂停执行,并返回结果。
yield 完成多任务
1 | import time |
线程依赖于进程,协程依赖于线程。
协程使用的是单线程,它只是充分利用延时的时间。
random 模块常用函数
random.randrange
random.randrange(stop)
random.randrange(start, stop[, step])
从 range([start], stop[, step])
中,即:[start, stop)
,步长为 step
的整数序列中随机选取一个整数。
random.randint
random.randint
(a, b)
从 [a, b] 中返回一个随机整数, 同:randrange(a, b+1)
1 | In [25]: random.randint(1,2) |
random.choice
Random.choice(seq)
从序列 seq 中随机选取一个元素(无论类型),如果 seq 为空,抛出异常。
1 | In [31]: random.choice(['8','6','4',3]) |
random.shuffle
Random.shuffle(x[, random])
对序列元素进行随机排序(直接改变原序列)
1 | In [37]: num = [1,2,3,4,5, 6] |