zoukankan      html  css  js  c++  java
  • 15 Python 迭代器和生成器

    什么是迭代 (iterable)

      字符串、列表、元组、字典、集合都可以被for循环,说明他们都是可迭代的

      可以直接作用于for循环的对象统称为可迭代对象(Iterable)。

      可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)。

      所有的Iterable均可以通过内置函数iter()来转变为Iterator。

      对迭代器来讲,有一个__next__()就够了。在你使用for 和 in 语句时,程序就会自动调用即将被处理的对象的迭代器对象,然后使用它的__next__()方法,直到监测到一个StopIteration异常。

      可迭代协议

        我们现在是从结果分析原因,能被for循环的就是“可迭代的”,但是如果正着想,for怎么知道谁是可迭代的呢?

        假如我们自己写了一个数据类型,希望这个数据类型里的东西也可以使用for被一个一个的取出来,那我们就必须满足for的要求。这个要求就叫做“协议”。

      可以被迭代要满足的要求就叫做可迭代协议。可迭代协议的定义非常简单,就是内部实现了__iter__方法。

     1 >>> L = [1,2,3]
     2 >>> [x**2 for x in L]
     3 [1, 4, 9]
     4 >>> next(L)
     5 Traceback (most recent call last):
     6   File "<stdin>", line 1, in <module>
     7 TypeError: 'list' object is not an iterator
     8 >>> I=iter(L)
     9 >>> next(I)
    10 1
    11 >>> next(I)
    12 2
    13 >>> next(I)
    14 3
    15 >>> next(I)
    16 Traceback (most recent call last):
    17   File "<stdin>", line 1, in <module>
    18 StopIteration
    View Code

      上面例子中,列表L可以被for进行循环但是不能被内置函数next()用来查找下一个值,所以L是Iterable。

      L通过iter进行包装后设为I,I可以被next()用来查找下一个值,所以I是Iterator。

    1. 内置函数iter()仅仅是调用了对象的__iter()方法,所以list对象内部一定存在方法iter__()
    2. 内置函数next()仅仅是调用了对象的__next()方法,所以list对象内部一定不存在方法next__(),但是Itrator中一定存在这个方法。
    3. for循环内部事实上就是先调用iter()把Iterable变成Iterator在进行循环迭代的
     1 >>> L = [4,5,6]
     2 >>> I = L.__iter__()
     3 >>> L.__next__()
     4 Traceback (most recent call last):
     5   File "<stdin>", line 1, in <module>
     6 AttributeError: 'list' object has no attribute '__next__'
     7 >>> I.__next__()
     8 4
     9 >>> from collections import Iterator, Iterable
    10 >>> isinstance(L, Iterable)
    11 True
    12 >>> isinstance(L, Iterator)
    13 False
    14 >>> isinstance(I, Iterable)
    15 True
    16 >>> isinstance(I, Iterator)
    17 True
    18 >>> [x**2 for x in I]    
    19 [25, 36]
    View Code

    迭代器

    迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。

    具有访问生成器的能力,可以访问到生成器的值,类似于生成器的__next__方法,一个一个值一个值得去迭代,只能够按照顺序的去查找。

    特点:

    1. 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
    2. 不能随机访问集合中的某个值 ,只能从头到尾依次访问
    3. 访问到一半时不能往回退
    4. 便于循环比较大的数据集合,节省内存
    1 print('__next__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__
    2 print('__iter__' in dir(range(12)))  #查看'__next__'是不是在range()方法执行之后内部是否有__next__
    3 
    4 from collections import Iterator
    5 print(isinstance(range(100000000),Iterator))  #验证range执行之后得到的结果不是一个迭代器
    View Code

    yield from

     1 def gen1():
     2     for c in 'AB':
     3         yield c
     4     for i in range(3):
     5         yield i
     6 
     7 print(list(gen1()))
     8 
     9 def gen2():
    10     yield from 'AB'
    11     yield from range(3)
    12 
    13 print(list(gen2()))
    View Code

    生成器

      1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

      2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

     

    生成器Generator:

      本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)

      特点:惰性运算,开发者自定义

    生成器函数

      一个包含yield关键字的函数就是一个生成器函数。yield可以为我们从函数中返回值,但是yield又不同于return,return的执行意味着程序的结束,调用生成器函数不会得到返回的具体的值,而是得到一个可迭代的对象。每一次获取这个可迭代对象的值,就能推动函数的执行,获取新的返回值。直到函数执行结束。

      仅仅拥有生成某种东西的能力,如果不用__next__方法是获取不到值得。

      创建一个生成器函数

     1 >>> def scq():
     2     print("11")
     3        # 当函数代码块中遇到yield关键字的时候,这个函数就是一个生成器函数
     4     yield 1
     5     print("22")
     6     yield 2
     7     print("33")
     8     yield 3
     9 
    10 
    11 # 把生成器赋值给一个对象
    12 
    13 >>> r = scq()
    14 
    15 # 查看r的苏剧类型并且输出r的值
    16 
    17 >>> print(type(r),r)
    18 <class 'generator'> <generator object scq at 0x000001F117D8DF10> 
    19 
    20 # 当执行生成器的__next__的时候,代码会按照顺序去执行,当执行到yield时会返回并
    21 # 提出,yield后面的值就是返回值,然后记录代码执行的位置,并退出
    22 
    23 >>> ret = r.__next__()
    24 11
    25 
    26 # 第二次执行的时候会根据上次代码执行的位置继续往下执行
    27 
    28 >>> ret = r.__next__()
    29 22
    30 >>> ret = r.__next__()
    31 33
    32 
    33 # 如果__next__获取不到值的时候就会报StopIteration错误
    34 
    35 >>> ret = r.__next__()
    36 Traceback (most recent call last):
    37   File "<stdin>", line 1, in <module>
    38 StopIteration
    View Code

      利用生成器创建一个range

     1 # 创建一个生成器函数,函数名是range,n是传入的参数,也是输出的数的最大值
     2 def range(n):
     3     # 默认从0开始
     4     start = 0
     5     # 进入while循环,如果最小值小于最大值就进入循环
     6     while start < n:
     7         # 第一次返回start,下面代码不执行
     8         yield start
     9         # 第二次进来的时候start = start + 1,然后进入下一次循环
    10         start += 1
    11 
    12 # 停止的参数为5
    13 obj = range(5)
    14 # 第一个数赋值给n1
    15 n1 = obj.__next__()
    16 # 第二个数赋值给n2
    17 n2 = obj.__next__()
    18 # 第三个数赋值给n3
    19 n3 = obj.__next__()
    20 # 第四个数赋值给n4
    21 n4 = obj.__next__()
    22 # 第五个数赋值给n5
    23 n5 = obj.__next__()
    24 
    25 # 输出这五个数的值
    26 print(n1,n2,n3,n4,n5)
    27 
    28 # 执行结果
    29 
    30 C:Python35python.exe F:/Python_code/sublime/Week5/Day03/s1.py
    31 0 1 2 3 4
    32 
    33 Process finished with exit code 0
    View Code

      生成器监听文件输入的例题

     1 import time
     2 
     3 
     4 def tail(filename):
     5     f = open(filename)
     6     f.seek(0, 2) #从文件末尾算起
     7     while True:
     8         line = f.readline()  # 读取文件中新的文本行
     9         if not line:
    10             time.sleep(0.1)
    11             continue
    12         yield line
    13 
    14 tail_g = tail('tmp')
    15 for line in tail_g:
    16     print(line)
    View Code

     计算移动平均值(1)

     1 def averager():
     2     total = 0.0
     3     count = 0
     4     average = None
     5     while True:
     6         term = yield average
     7         total += term
     8         count += 1
     9         average = total/count
    10 
    11 
    12 g_avg = averager()
    13 next(g_avg)
    14 print(g_avg.send(10))
    15 print(g_avg.send(30))
    16 print(g_avg.send(5))
    View Code

     计算移动平均值(2)预激协程的装饰器

     1 def init(func):  #在调用被装饰生成器函数的时候首先用next激活生成器
     2     def inner(*args,**kwargs):
     3         g = func(*args,**kwargs)
     4         next(g)
     5         return g
     6     return inner
     7 
     8 @init
     9 def averager():
    10     total = 0.0
    11     count = 0
    12     average = None
    13     while True:
    14         term = yield average
    15         total += term
    16         count += 1
    17         average = total/count
    18 
    19 
    20 g_avg = averager()
    21 # next(g_avg)   在装饰器中执行了next方法
    22 print(g_avg.send(10))
    23 print(g_avg.send(30))
    24 print(g_avg.send(5))
    View Code

    列表推导式和生成器表达式

     1 #老男孩由于峰哥的强势加盟很快走上了上市之路,alex思来想去决定下几个鸡蛋来报答峰哥
     2 
     3 egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析
     4 
     5 #峰哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下
     6 
     7 laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
     8 print(laomuji)
     9 print(next(laomuji)) #next本质就是调用__next__
    10 print(laomuji.__next__())
    11 print(next(laomuji))
    View Code

    各种推导式详解

      推导式的套路

        之前我们已经学习了最简单的列表推导式和生成器表达式。但是除此之外,其实还有字典推导式、集合推导式等等。

        下面是一个以列表推导式为例的推导式详细格式,同样适用于其他推导式。

    1 variable = [out_exp_res for out_exp in input_list if out_exp == 2]
    2   out_exp_res:  列表生成元素表达式,可以是有返回值的函数。
    3   for out_exp in input_list:  迭代input_list将out_exp传入out_exp_res表达式中。
    4   if out_exp == 2:  根据条件过滤哪些值可以。
    View Code

    列表推导式

      例一:30以内所有能被3整除的数

    1 multiples = [i for i in range(30) if i % 3 is 0]
    2 print(multiples)
    3 # Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
    View Code

      例二:30以内所有能被3整除的数的平方

    1 def squared(x):
    2     return x*x
    3 multiples = [squared(i) for i in range(30) if i % 3 is 0]
    4 print(multiples)
    View Code

      例三:找到嵌套列表中名字含有两个‘e’的所有名字

    1 names = [['Tom', 'Billy', 'Jefferson', 'Andrew', 'Wesley', 'Steven', 'Joe'],
    2          ['Alice', 'Jill', 'Ana', 'Wendy', 'Jennifer', 'Sherry', 'Eva']]
    3 
    4 print([name for lst in names for name in lst if name.count('e') >= 2])  # 注意遍历顺序,这是实现的关键
    View Code

    字典推导式

      例一:将一个字典的key和value对调

    1 mcase = {'a': 10, 'b': 34}
    2 mcase_frequency = {mcase[k]: k for k in mcase}
    3 print(mcase_frequency)
    View Code

      例二:合并大小写对应的value值,将k统一成小写

    1 mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}
    2 mcase_frequency = {k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0) for k in mcase.keys()}
    3 print(mcase_frequency)
    View Code

    集合推导式

      例:计算列表中每个值的平方,自带去重功能

    1 squared = {x**2 for x in [1, -1, 2]}
    2 print(squared)
    3 # Output: set([1, 4])
    View Code

    练习题:

      例1:  过滤掉长度小于3的字符串列表,并将剩下的转换成大写字母

      例2:  求(x,y)其中x是0-5之间的偶数,y是0-5之间的奇数组成的元祖列表

      例3:  求M中3,6,9组成的列表M = [[1,2,3],[4,5,6],[7,8,9]]

    1 1.[name.upper() for name in names if len(name)>3] 
    2 2.[(x,y) for x in range(5) if x%2==0 for y in range(5) if y %2==1] 
    3 3. [row[2] for row in M]
    View Code

    总结:

      1.把列表解析的[]换成()得到的就是生成器表达式

      2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

      3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

    1 sum(x ** 2 for x in range(4))
    2 
    3 # 而不用多此一举的先构造一个列表:
    4 
    5 sum([x ** 2 for x in range(4)]) 
    View Code

    可迭代对象:

      拥有__iter__方法

      特点:惰性运算

      例如:range(),str,list,tuple,dict,set

    迭代器Iterator:

      拥有__iter__方法和__next__方法

      例如:iter(range()),iter(str),iter(list),iter(tuple),iter(dict),iter(set),reversed(list_o),map(func,list_o),filter(func,list_o),file_o

    生成器Generator:

      本质:迭代器,所以拥有__iter__方法和__next__方法

      特点:惰性运算,开发者自定义

    使用生成器的优点:

    1.延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

    2.提高代码可读性

    1 #列表解析
    2 sum([i for i in range(100000000)])#内存占用大,机器容易卡死
    3  
    4 #生成器表达式
    5 sum(i for i in range(100000000))#几乎不占内存
    View Code

    生成器相关的面试题

      生成器在编程中发生了很多的作用,善用生成器可以帮助我们解决很多复杂的问题

      除此之外,生成器也是面试题中的重点,在完成一些功能之外,人们也想出了很多魔性的面试题。
      面试题(1)

     1 def demo():
     2     for i in range(4):
     3         yield i
     4 
     5 g=demo()
     6 
     7 g1=(i for i in g)
     8 g2=(i for i in g1)
     9 
    10 print(list(g1))
    11 print(list(g2))
    View Code

      面试题(2)

     1 def add(n,i):
     2     return n+i
     3 
     4 def test():
     5     for i in range(4):
     6         yield i
     7 
     8 g=test()
     9 for n in [1,10]:
    10     g=(add(n,i) for i in g)
    11 
    12 print(list(g))
    View Code

      tail&grep

     1 def demo():
     2     for i in range(4):
     3         yield i
     4 
     5 g=demo()
     6 
     7 g1=(i for i in g)
     8 g2=(i for i in g1)
     9 
    10 print(list(g1))
    11 print(list(g2))
    12 
    13 复制代码
    14 复制代码
    15 
    16 def add(n,i):
    17     return n+i
    18 
    19 def test():
    20     for i in range(4):
    21         yield i
    22 
    23 g=test()
    24 for n in [1,10]:
    25     g=(add(n,i) for i in g)
    26 
    27 print(list(g))
    28 
    29 复制代码
    30 复制代码
    31 
    32 import os
    33 
    34 def init(func):
    35     def wrapper(*args,**kwargs):
    36         g=func(*args,**kwargs)
    37         next(g)
    38         return g
    39     return wrapper
    40 
    41 @init
    42 def list_files(target):
    43     while 1:
    44         dir_to_search=yield
    45         for top_dir,dir,files in os.walk(dir_to_search):
    46             for file in files:
    47                 target.send(os.path.join(top_dir,file))
    48 @init
    49 def opener(target):
    50     while 1:
    51         file=yield
    52         fn=open(file)
    53         target.send((file,fn))
    54 @init
    55 def cat(target):
    56     while 1:
    57         file,fn=yield
    58         for line in fn:
    59             target.send((file,line))
    60 
    61 @init
    62 def grep(pattern,target):
    63     while 1:
    64         file,line=yield
    65         if pattern in line:
    66             target.send(file)
    67 @init
    68 def printer():
    69     while 1:
    70         file=yield
    71         if file:
    72             print(file)
    73 
    74 g=list_files(opener(cat(grep('python',printer()))))
    75 
    76 g.send('/test1')
    77 
    78 协程应用:grep -rl /dir
    View Code
  • 相关阅读:
    西瓜书的读书笔记
    Tensorflow-gpu在windows10上的安装(anaconda)
    php 设置报错等级
    PHP 类型比较表
    PHP中的定界符格式
    php进制转换函数
    php魔法常量
    php中双$$与多$$
    php引用传值
    CSS3过渡效果实现菜单划出效果
  • 原文地址:https://www.cnblogs.com/panfb/p/7811270.html
Copyright © 2011-2022 走看看