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 次)调用跳转至该函数中间,而上次调用的所有局部变量都保持不变。

生成器不仅“记住”了它数据状态;生成器还“记住”了它在流控制构造(在命令式编程中,这种构造不只是数据值)中的位置。

生成器的特点:

  1. 节约内存
  2. 迭代到下一次的调用时,所使用的参数都是第一次所保留下的,即是说,在整个所有函数调用的参数都是第一次所调用时保留的,而不是新创建的

 




8.迭代器

可以直接作用于for循环的数据类型:

  • 一类是集合数据类型,如listtupledictsetstr等;
  • 一类是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对象,但listdictstr虽然是Iterable,却不是Iterator

listdictstrIterable变成Iterator可以使用iter()函数:

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

  

  

  (3)小结

凡是可作用于for循环的对象都是Iterable类型;

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

集合数据类型如listdictstr等是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

  

 

posted @ 2018-02-08 13:55  venicid  阅读(244)  评论(0编辑  收藏  举报