13-[函数进阶]-列表生成式,生成器&迭代器
1.列表生成式
Python一种独特的语法,相当于语法糖的存在,可以帮你在某些场合写出比较精简酷炫的代码。但没有它,也不会有太多的影响。
语法糖(Syntactic sugar),也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。
- 需求:把a=[0,1,2,3,4,5,6,7,8,9]里面的每个元素+1
# sb版本 >>> b = [] >>> for i in a:b.append(i+1) >>> a = b # 普通版本 a = [1,3,4,6,7,7,8,9,11] for index,i in enumerate(a): a[index] +=1 print(a) # 文艺版本 >>> a = map(lambda x:x+1,a) >>> a <map object at 0x02861810> >>> list(a) [2, 3, 4, 5, 6, 7, 8, 9]
- 装B版本
>>> a = [i+1 for i in range(10)] >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
这样的写法就叫做列表生成式
面试真题
看下面代码回答输出的结果是什么?为什么?
result = [lambda x: x + i for i in range(10)] print(result[0](10))
这是一个结合了变量作用域、列表推导式和匿名函数的题目,较为复杂,比较考验Python基础知识掌握程度。有同学可能会回答10,其实答案是19,并且result[0~9](10)
的结果都是19。
这是因为函数具有调用时才查找变量的特性。在你没调用它之前,它不会保存也不关心它内部变量的具体值。只有等到你调用它的时候,它才逐一去找这些变量的具体值。这里的result[0]被调用的时候,变量i已经循环完毕,变成9了,而不是想象中的动态0-9值。
那如果不想要这样的结果,想要i是循环的值怎么办?不要直接引用上层变量,把变量直接传进来。
result = [lambda x, i=i: x + i for i in range(10)] print(result[0](10))
2.生成器
想生成一个存放很多数据的列表,但是又不想内存占用太多,最好每次用一个生成一个
在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
- 方法1,只要把一个列表生成式的
[]
改成(),
就创建了一个generator: - 方法2:yield 把函数变成生成器
>>> list1 = [i for i in range(100)] >>> list1 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27 , 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> li = (i for i in range(100)) >>> li <generator object <genexpr> at 0x0220C6E8>
- 获取生成器的值:next(li) 和 li.__next__()
>>> li.__next__() 0 >>> li.__next__() 1 >>> li.__next__() 2
>>> next(li) 8 >>> next(li) 9 >>> next(li) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration #每次调用next(g)就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
3.range也是生成器
Python 2.7.12 (default, Jul 1 2016, 15:12:24) [GCC 5.4.0 20160609] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> >>> range(10) # 直接生成list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>> >>> xrange(10) #一个生成器,每次调用取值 xrange(10) >>> >>> list(xrange(10)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
range(...) range(stop) -> list of integers range(start, stop[, step]) -> list of integers
4.打开文件的f也是生成器
4.斐波那契数列
(1)2B版本
a = 0 b = 1 count = 0 while True: b,a = a+b,b print(a) if b > 200: break count += 1
(2)函数版本
def fib(max): a, b = 0, 1 count = 0 while count < max: b, a = a+b, b print(a) count += 1 return 'done' fib(10) # 打印10次
5.yield 函数版生成器
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。
而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次被next()调用时从上次返回的yield语句处继续执行。
6.生成器send方法
例子:执行到yield时,gen函数作用暂时保存,返回i的值;temp接收下次c.send("python"),send发送过来的值,c.next()等价c.send(None)
def fib(max): a, b, n = 0, 1, 0 while n < max: send_msg = yield b print(send_msg) a, b = b, a+b n += 1 f = fib(10) print(next(f)) f.send('寄个快递') print(next(f))
In [29]: f.send(None) #第一次相当于f.__next__()
7还可通过yield实现在单线程的情况下实现并发运算的效果
#_*_coding:utf-8_*_ __author__ = 'Alex Li' import time def consumer(name): print("%s 准备吃包子啦!" %name) while True: baozi = 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") # 通过生成器实现协程并行运算
8.生成器总结
生成器是这样一个函数,它记住上一次返回时在函数体中的位置。对生成器函数的第二次(或第 n 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。
生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。
生成器的特点:
- 节约内存
- 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的
8.迭代器
可以直接作用于for
循环的数据类型:
- 一类是集合数据类型,如
list
、tuple
、dict
、set
、str
等; - 一类是
generator
,包括生成器和带yield
的 generator function。
(1)可迭代对象
直接作用于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
(2)迭代器:是一种数据流
可以被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
(3)小结
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。
- Python3的
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