1-1
列表生成式
>>> a = [1,2,3]
>>> a
[1, 2, 3]
>>>
>>> [i*2 for i in range (10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# for i in range(10) 这是一个循环,循环 10 次
# i*2 中的 i 就是 for 循环中的 i
# 把 for 循环中的临时变量 i 赋值到前面的 i*2 的 i 中,得到的结果,就是列表中的元素
# 这个列表 和 列表 a 的区别是:列表 a 是写死的,下面的 i*2 是可以进行操作的
1-1-2
# [i*2 for i in range(10)] 相当于
>>> a = []
>>> for i in range (10):
... a.append(i*2)
...
>>> a
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# 相当于至少 3 行代码
# [i*2 for i in range(10)] 就叫 列表生成式
列表生成式的主要作用就是使代码更简洁
1-1-3
当然列表生成式也可以传函数,比如
>>> [func(i) for i in range (10)]
1.迭代器&生成器
生成器
通过列表生成式,我们可以直接创理一个列表。但是,受到内存限制,列表容量肯定是有限的。
而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们识仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以, 如果列表元素可以按照某种算法推算出来
那我们是否可以在循环的过程中不断推算出后续的元素呢?
这样就不必创建完整的list,从而节省大量的空间。
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator, 有很多种方法。
第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator;
# 在 python2 上 range(10) 数据已经准备好了
# 在 python3 上 range(10) 是没有准备好的
# 现在希望在循环的时候,循环到 列表的 第 4 个元素时,这个元素才刚生成;
# 这样,就不用把所有数据都提前准备好了,就可以节省空间
2-1
>>> [i*2 for i in range (10)]
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
>>> (i*2 for i in range (10))
<generator object <genexpr> at 0x0000027B0E6C3BC8>
# 这就变成了一个生成器,对这个数据进行循环时,每循环一次就按此规律乘 2
2-1-2
>>> b = (i*2 for i in range (10))
>>> for i in b:
... print (i)
...
0
2
4
6
8
10
12
14
16
18
>>>
# 这里的数据量比较少,所以也许无法看出差异;但如果数据量达到100个以上,这种方法可以马上返回数据
# 这时,如果继续用以往的形式,就会感觉到慢了
2-1-3
>>> c = ( i*2 for i in range (100000000))
>>> c
<generator object <genexpr> at 0x0000027B0E6F72C8>
>>>
# 事实上,这里并没有生成数据,只是返回了一个地址,除非访问这个地址,才会生成数据
# 如果不访问,这个数据就不会存在;
# 而列表则是把所有数据都准备好的;而生成器则是准备了一个算法,算法写好了一个规则,调用时,才会产生数据
2-1-4
# 现在想访问 c 的第 1000 个数据,前面的 999 个数据要被循环到,才能够调用;否则无法直接一下调用第 1000 个
# 所以它不支持列表的那种切片
>>> c[1000]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable
2-1-5
# 既然生成器不支持切片,那生成器能够支持什么呢?
# 除了用 for 循环一个一个的取,还支持别的方式吗?
# 没有,只能用 for 循环这一种方式获取
2-1-6
# 如果只访问前两个数据,应该怎么做呢?
# 生成器,有一个方法,叫做 __next__() 方法
>>> c = ( i*2 for i in range (100000000))
>>> for i in c:
... print (i)
...
0
2
4
……
2210
2212
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
KeyboardInterrupt
>>> c. __next__ ()
2214
>>> c. __next__ ()
2216
>>> c. __next__ ()
2218
>>> c. __next__ ()
2220
>>>
# 现在要回去取 2210,是不可以的,回去取是不行的;而且也没有任何相应的方法让你回到上一个
# 生成器只记住当前位置,它不知道前面,也不知道后面;前面用完了,就没了
# 所以既不能往前走,也不能往后退;只能一点一点的往后走
1 生成器 只有在调用时才会生成相应的数据
2 只记录当前位置
3 只有一个 __next__ ()方法。(在 python2.7 中是 next ())
所以,我们创建了一个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]
仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第
一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似 generator
也就是说,上面的函数和generator仅一步之遥。
要把 fib 函数变成 generator,只需要把 print (b) 改为 yield b 就可以了:
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'
3-1
def fib (max):
n,a,b = 0,0,1
while n < max :
print (b)
a, b = b, a + b
n = n + 1
return 'done'
fib(10) # 10 就表示从 1 开始,生成 10 个 斐波那契数列
--->
1
1
2
3
5
8
13
21
34
55
3-1-2
>>> a,b = 1,2
>>> a
1
>>> b
2
>>> t = (b,a+b)
>>> t
(2, 3)
>>>
3-1-3
# 一个微妙之处
>>> foo = 'Monty'
>>> bar = foo
>>> foo = 'Python'
>>> bar
'Monty'
>>>
这个结果与预期的完全一样。当在代码中写入bar=foo时,foo的值(字符串'Monty')被赋值给bar。也就是说,bar是foo的一个副本,所以当第2行用一个新
的字符串'Python'覆盖foo时,bar的值不会受到影响。
3-1-4-1
>>> a=0
>>> b=1
>>> a,b=b,a+b
>>> a
1
>>> b
1
3-1-4-2
>>> c=0
>>> d=1
>>> c=d
>>> d=c+d
>>> c
1
>>> d
2
3-2
# 现在把这个 fib 函数,变成生成器
def fib (max):
n,a,b = 0,0,1
while n < max :
# print(b)
yield b
a, b = b, a + b
n = n + 1
return 'done'
print (fib(100))
--->
<generator object fib at 0x0000018C3144A7C8>
# 就变成生成器了
3-2-1
def fib (max):
n,a,b = 0,0,1
while n < max :
# print(b)
yield b
a, b = b, a + b
n = n + 1
# return 'done'
f = fib(100)
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
--->
1
1
2
# 之前一个函数一旦调用,就需要等待执行结束,才可以继续执行后面的内容
# 现在用生成器,就可以在 函数没有完全执行完的过程中 “出出进进”
3-2-2
def fib (max):
n,a,b = 0,0,1
while n < max :
# print(b)
yield b
a, b = b, a + b
n = n + 1
# return 'done'
f = fib(100)
print (f. __next__ ())
print ("===1===")
print (f. __next__ ())
print ("===2===")
print (f. __next__ ())
print ("====start loop")
for i in f:
print (i)
--->
1
===1===
1
===2===
2
====start loop
3
5
8
…… ……
# 这样就不需要等函数执行完毕了,而是可以让函数停在一个地方;可以中断函数去做别的事情,然后在回来执行函数
3-3
def fib (max):
n,a,b = 0,0,1
while n < max :
# print(b)
yield b
a, b = b, a + b
n = n + 1
return 'done'
f = fib(10)
print (f. __next__ ())
print ("=======")
print (f. __next__ ())
print ("====start loop")
for i in f:
print (i)
--->
1
=======
1
====start loop
2
3
5
8
13
21
34
55
# 可以看出,done 并没有被打印;使用 for 循环,done 就不会被打印
3-3-1
# 那么,如果不用 for 循环呢?这里,有 10 组数,打印 11 个,让它报错,会发生什么呢?
def fib (max):
n,a,b = 0,0,1
while n < max :
# print(b)
yield b
a, b = b, a + b
n = n + 1
return 'done'
f = fib(10)
print (f. __next__ ())
print ("=======")
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
print (f. __next__ ())
--->
1
=======
1
2
3
5
8
13
21
34
55
Traceback (most recent call last):
File "c:/Users/Majesty/Desktop/2.py", line 22, in <module>
print (f. __next__ ())
StopIteration: done
# 抛出了 一个包含 done 的异常
3-3-2
用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 = n + 1
return 'done'
g=fib(6)
while True:
try:
x= next (g)
print ('g:', x)
except StopIteration as e:
print ('Generator returnvalue:', e.value)
break
--->
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator returnvalue: done
# 这就是 return 在生成器中的作用;只要有 yield 它就是一个生成器,就不叫函数了
# 单线程就是串行执行
# 通过生成器可以实现并行效果
def fib (max):
n,a,b = 0,0,1
while n < max :
# print(b)
yield b
a, b = b, a + b
n = n + 1
return 'done'
# 想把谁返回到外面,就把 yield 放到哪
# 只要执行到 yield b,程序就会中断,函数中断后,就会回到外面
# 回到外面后,通过 next(g) 就又回到了函数里
# 所以 yield 是保存了 函数的中断状态,返回当前状态的值
g=fib(6)
while True:
try:
x= next (g) # next()也是一个内置方法,和 __next__()是一样的
print ('g:', x)
except StopIteration as e:
print ('Generator returnvalue:', e.value)
break
1-1
生成器还可通过 yield 实现在单线程的情况下实现并发运算的效果
#_*_coding:utf-8_*_
__author__='Alex Li'
import time
def consumer (name) :
print ("%s 准备吃包子啦!" %name)
while True:
baozi = yield
# 这里 yield 没有返回值,没有返回值,结果就是空
print ("包子[%s]来了,被[%s]吃了!" %(baozi,name))
def producer (name) :
c=consumer('A')
c2=consumer('B')
c. __next__ ()
c2. __next__ ()
print ("老子开始准备做包子啦!")
for i in range (10) :
time.sleep(1)
print ("做了2个包子!")
c.send(i)
c2.send(i)
producer("alex")
# 这是一个典型的生产者,消费者模型
1-1-2
import time
def consumer (name):
print ("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print ("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("ChenRonghua") # 这样就变成一个生成器了
c. __next__ ()
--->
ChenRonghua 准备吃包子啦!
1-1-3
import time
def consumer (name):
print ("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print ("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("ChenRonghua") # 这样就变成一个生成器了
c. __next__ ()
c. __next__ ()
--->
ChenRonghua 准备吃包子啦!
包子[None]来了,被[ChenRonghua]吃了!
# None 代表空,可是没有包子吃个毛线? 所以,可以这样
1-1-4
import time
def consumer (name):
print ("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print ("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("ChenRonghua") # 这样就变成一个生成器了
c. __next__ ()
b1 = "韭菜馅"
c.send(b1)
# 把 b1 发送过去,通过 send 函数,包子就被传进去了;传进去后,被 yield 接收到了
# 接收到后,将值付给了 baozi
# yield 的作用是保存当前状态,然后返回,返回后调用 next,返回 yield;但是 调用 next 并不会传值
# 但是 send 可以传值;
# next 和 send 的区别:1. next 只是再次调用 yield,但是不会传值; 2. send 调用 yield 的同时给 yield 传值
--->
ChenRonghua 准备吃包子啦!
包子[韭菜馅]来了,被[ChenRonghua]吃了!
1-2-1
# 现在要边做边吃,看一个并行的效果
import time
def consumer (name):
print ("%s 准备吃包子啦!" %name)
while True:
baozi = yield
print ("包子[%s]来了,被[%s]吃了!" %(baozi,name))
c = consumer("ChenRonghua") # 这样就变成一个生成器了
c. __next__ ()
b1 = "韭菜馅"
c.send(b1)
def producer (name): # producer 就是一个生产包子的
c = consumer('A') # c 和 c2 相当于 两个消费者
c2 = consumer('B')
c. __next__ ()
c2. __next__ ()
print ("老子开始准备做包子啦!")
for i in range (4):
time.sleep(1)
print ("做了2个包子!") # 没一秒钟做两个包子
c.send(i)
c2.send(i) # 分别把 包子 送给 c 和 c2 两个消费者
producer("alex")
--->
ChenRonghua 准备吃包子啦!
包子[韭菜馅]来了,被[ChenRonghua]吃了!
A 准备吃包子啦!
B 准备吃包子啦!
老子开始准备做包子啦!
做了2个包子!
包子[0]来了,被[A]吃了!
包子[0]来了,被[B]吃了!
做了2个包子!
包子[1]来了,被[A]吃了!
包子[1]来了,被[B]吃了!
做了2个包子!
包子[2]来了,被[A]吃了!
包子[2]来了,被[B]吃了!
做了2个包子!
包子[3]来了,被[A]吃了!
包子[3]来了,被[B]吃了!
# 这就是单线程下的并行效果;虽然还是依次执行的,但可以在不同角色之间来回切换;看起来就好像是并行的
# 这个就是 异步 IO 的雏形,nginx的效率高,就因为它是异步,底层的原理用的就是这个
# nginx在单线程下,承载的并发量,比多线程还要快;本质上就是通过这样的效果实现的
这种情况,不把它叫做 yield,把它叫做 协程;协程是比线程更小的单位;这个可以理解为是最简单的协程
# 前面生成器就彻底结束了
迭代器
我们已经知道, 可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型, 如list、tuple、dict、set、str等;
一类是generator(数据结构),包括生成器和带yield的generator function.
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。 (可迭代对象可以简单理解为可循环对象)
可以使用isinstance()判断一个对象是否是Iterable对象:
(Iter 迭代,Iterable 可迭代的)
>>> from collections import Iterable
>>> isinstance ([],Iterable)
True
>>> isinstance ('abc',Iterable)
True
>>> isinstance ((),Iterable)
True
>>> isinstance (10,Iterable)
False
>>> isinstance ((x for x in range (10)),Iterable)
True
而生成器不但可以作用于for循环, 还可以被next() 函数环断调用并返回下一个值,
直到最后抛出StopIteration错误表示无法继续返回下一个值了。
*可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator.
可以使用isinstance()判断一个对象是否是Iterator对象:
# 可以for循环的对象称为可迭代对象;可以被 next()函数调用并不断返回下一个值的对象称为迭代器
# 可迭代 Iterable 对象和迭代器 Iterator 并不是一回事
1-1
>>> a = [1,2,3]
# 是可迭代对象,但并不是 迭代器;因为它没有 next() 方法
1-1-2
# 通过 dir 命令,可以查看它所有的可以调用的方法
>>> a = [1,2,3]
>>> dir (a)
['__add__', '__class__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__',
'__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__',
'__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__',
'__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__',
'__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append',
'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
>>>
# 这个列表中,没有 next 方法,没有 next 方法,就不叫迭代器
# 有 next 方法,就叫 迭代器
# 可以使用 isinstance() 判断是否为 迭代器 对象
1-1-3
>>> from collections import Iterator
>>> isinstance ((x for x in range (5)),Iterator)
True
# 一个生成器就是一个迭代器,因为生成器有 next 方法
# 但迭代器不一定是生成器,有next方法不一定是生成器
# 所以,列表、字典、元组等 就一定不是迭代器
2-1
生成器都是Iterator对象, 但list、dict、str虽然是Iterable,却不是Iterator.
把list、dict、str等Iterable变成Iterator可以使用iter()函数:
>>> isinstance ( iter ([]),Iterator)
True
>>> isinstance ( iter ('abc'),Iterator)
True
>>>
2-1-2
>>> a
[1, 2, 3]
>>> iter (a)
<list_iterator object at 0x000001EAA35F4D88>
>>> b= iter (a)
>>> b. __next__ ()
1
>>> b. __next__ ()
2
>>> b. __next__ ()
3
# 字典和字符串也是如此
2-2
你可能会问, 为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,
直到没有数据时抛出StopIteration错误。
# 列表一定有开始和结束
# 而数据流默认生成时,可以认为是一个流,没有固定的结束点
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next() 函数实现按需计算下一个数据,
所以Iterator的计算是情性的,只有在需要返回下一个数据时它才会计算。
# 有一种计算叫做 惰性计算,即走到这一步才计算,没有走到这一步就不会计算
# 而链表都是计算好的,所以不是惰性计算
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
# 因为 iterator 根本不知道结束点在哪里
小结
凡是可作用于for循环的对象都是Iterable类型:
凡是可作用于next()函数的对象都是Iterator类型, 它们表示一个惰性计算的序列:
集合数据类型 如list、dict、str等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。
Python的 for 循环 本质上就是通过 不断调用 next () 函数实现的, 例如:
>>> for x in [1,2,3,4,5]:
... pass
实际上完全等价于:
#首先获得Iterator对象:
it= iter ([1,2,3,4,5])
#循环:
while True:
try:
# 获得下一个值:
x= next (it)
except StopIteration:
# 遇到StopIteration就退出循环
break
2-3
>>> range (10)
range (0, 10)
# 立刻返回一个列表,其实这个在python3.0中就是一个迭代器;在python2中 xrange(10)是一个迭代器;range(10)返回列表
# for循环本质上就是迭代器,只不过是封装了,所以看不出来
查看更多关于20210330 迭代器与生成器的详细内容...