zoukankan      html  css  js  c++  java
  • 流畅的python,Fluent Python 第十四章笔记 (可迭代的对象、迭代器和生成器)

    迭代是数据处理的基石。扫描内存中放不下的数据集时,我们要找到一种惰性获取数据的方式,既按需一次获取一个数据项。这就是迭代器模式。

    所有的生成器都石迭代器,因为生成器完全实现了迭代器的接口。不过根据《设计模式:可复用面向对象软件的基础》一书的定义,迭代器用于从集合中取出元素;而生成器用于"凭空"生成元素。

    通过斐波那契数列能很好的说明二者之间的区别:斐波那契数列中的数有无穷个,在一个集合里放不下。不过要知道,再Python社区中,大多数时候把迭代器和生成器视为同一个概念。

    14.1Sentence类第1版:单词排序

    import re
    import reprlib
    
    RE_WORD = re.compile('w+')
    
    
    class Sentence:    # 定义成序列的协议,有__getitem__与__len__
    
        def __init__(self, text):
            self.text = text
            self.word = RE_WORD.findall(text)
    
        def __getitem__(self, item):
            return self.word[item]
    
        def __len__(self):
            return len(self.word)
    
        def __repr__(self):
            return f'{type(self).__name__}({reprlib.repr(self.text)})'
    

     运行结果:

    In [5]: s = Sentence('"The time has come," the Walrus said')                                         
    
    In [8]: s                                                                                            
    Out[8]: Sentence('"The time ha...e Walrus said')
    
    In [9]: for word in s: 
       ...:     print(word) 
       ...:                                                                                              
    The
    time
    has
    come
    the
    Walrus
    said
    
    In [10]: list(s)                                                                                     
    Out[10]: ['The', 'time', 'has', 'come', 'the', 'Walrus', 'said']
    
    In [11]:  
    

    序列可以迭代的原因:iter函数

    解释器需要迭代对象x时,会自动调用iter()

    内置的iter函数有以下作用:

    1、检查对象是否实现了__iter__方法,如果实现了就调用它,获取一个迭代器

    2、如果没有实现__iter__方法,但是实现了__getitem__方法,Python会创建一个迭代器,尝试按顺序(从索引0开始)获取元素

    3、如果尝试失败,Python抛出TypeError异常。

    前面讲过,鸭子类型(duck typing)的极端形式:只要实现特殊的__iter__方法,或者实现__getitem__方法且__getitem__方法的参数是从0开始的整数(int),就可以认为是可迭代的。

    abc.Iterable实现了__subclasshook__方法,可以来测试是不是可迭代对象。但不是很准确,因为如果就像前面定义的Sentence没有__iter__方法,它就认为该对象不属于可迭代对象。

    因为用iter()函数测试,只要能被iter()通过的,都是可迭代对象,可以用try,except来测试,其实用list来测试也可以。

    In [13]: from collections.abc import Iterable                                                        
    
    In [14]: isinstance('',Iterable)                                                                     
    Out[14]: True
    
    In [15]: isinstance(dict(),Iterable)                                                                 
    Out[15]: True
    
    
    In [17]: isinstance(s,Iterable)                                                                      
    Out[17]: False
    
    In [18]: iter(s)                                                                                     
    Out[18]: <iterator at 0x10ea46ed0>
    

    14.2可迭代的对象与迭代器的对比

    可迭代的对象,使用iter内置函数可以获取迭代器对象。如果对象实现了能返回迭代器的__iter__方法,那么该对象就是可迭代的。

    简单来说,只要有了__irer__方式的对象就是可迭代对象。

    序列是可以迭代,实现了__getotem__方法,而且其参数是从零开始的索引,这种对象也可以迭代。

    In [32]: s = 'abc'                                                                                   
    
    In [33]: for i in s: 
        ...:     print(i) 
        ...:                                                                                             
    a
    b
    c
    
    In [34]: it = iter(s)                                                                                
    
    In [35]: while True: 
        ...:     try: 
        ...:         print(next(it)) 
        ...:     except StopIteration: 
        ...:         del it 
        ...:         break 
        ...:                                                                                             
    a
    b
    c
    
    In [36]:                                                                                             
    

     for循环的轨迹,后面用while循环显示出来,我记得我以前看过一本数,说for循环其实就while循环的一种语法糖。

    StopIteration异常表明迭代器到头了。Python语言内部会处理for循环和其他迭代上下文(如列表推导、元祖拆包、等等)中的StopIteraton异常

    在colletions.abc的Iterable与Iterator中,两个都有__subclasshoon__方法,其中Iterator是Iterable的子类。

    在Iterator的__subclasshoon__方法,可以让你不用继承该类去测试对象是否为迭代器。

    迭代器是这样的对象:实现了午餐书的__next__方法,返回序列中的下一个元素;如果没有元素了,那么爆出StopIteration异常。Python中的迭代器还实现了__iter__方法,因为迭代器也是可以迭代的。

    14.3 Sentence第二版:典型的迭代器

    import re
    import reprlib
    
    RE_WORD = re.compile('w+')
    
    
    class Sentence:    # 定义成序列的协议,有__getitem__与__len__
    
        def __init__(self, text):
            self.text = text
            self.word = RE_WORD.findall(text)
    
        def __len__(self):
            return len(self.word)
    
        def __repr__(self):
            return f'{type(self).__name__}({reprlib.repr(self.text)})'
    
        def __iter__(self):
            return SentenceIterator(self.word)
    
    
    
    class SentenceIterator:     # 返回一个迭代器
    
        def __init__(self, words):
            self.words = words
            self.count = 0
    
        def __next__(self):
            try:
                word =  self.words[self.count]
            except IndexError:
                raise StopIteration
            self.count += 1
            return word
    
        def __iter__(self):
            return self
    

     运行结果:

    In [37]: s = Sentence('"The time has come," the Walrus said')                                        
    
    In [39]: s                                                                                           
    Out[39]: Sentence('"The time ha...e Walrus said')
    
    In [40]: it = iter(s)                                                                                
    
    In [42]: next(it)                                                                                    
    Out[42]: 'The'
    
    In [43]: next(it)                                                                                    
    Out[43]: 'time'
    
    In [44]: list(it)                                                                                    
    Out[44]: ['has', 'come', 'the', 'Walrus', 'said']
    
    In [45]: it                                                                                          
    Out[45]: <__main__.SentenceIterator at 0x10e8cd8d0>
    
    
    
    In [46]: next(it)                                                                                    
    ---------------------------------------------------------------------------
    IndexError                                Traceback (most recent call last)
    <ipython-input-36-1416aa8893b4> in __next__(self)
         31         try:
    ---> 32             word =  self.words[self.count]
         33         except IndexError:
    
    IndexError: list index out of range
    
    During handling of the above exception, another exception occurred:
    
    StopIteration                             Traceback (most recent call last)
    <ipython-input-46-bc1ab118995a> in <module>
    ----> 1 next(it)
    
    <ipython-input-36-1416aa8893b4> in __next__(self)
         32             word =  self.words[self.count]
         33         except IndexError:
    ---> 34             raise StopIteration
         35         self.count += 1
         36         return word
    
                                                                                        
    

    这是一种解剖式的for循环的时候,__iter__在干什么。

    其实每次获取迭代对象的内容,书面语(为了支持多种遍历),必须能从同一个可迭代的实现中获取多个独立的迭代器,而且各个迭代器要能维护自身的内部状态,因此这一模式正确的实现方法是,每次调用iter都能新建一个独立的迭代器。

    可迭代的对象一定不能是自身的迭代器。也就是说,可迭代对象不许实现__iter__方法,但不能实现__next__方法。

    14.4 Sentence类第3版:生成器函数

    import re
    import reprlib
    
    RE_WORD = re.compile('w+')
    
    
    class Sentence:    # 定义成序列的协议,有__getitem__与__len__
    
        def __init__(self, text):
            self.text = text
            self.word = RE_WORD.findall(text)
    
        def __len__(self):
            return len(self.word)
    
        def __repr__(self):
            return f'{type(self).__name__}({reprlib.repr(self.text)})'
    
        def __iter__(self):    # 生成器函数
            for word in self.word:
                yield word
            return              # 这个return可以不写,因为生成器在函数体执行完毕后自动抛出StopIteration
    

     上面将__iter__变成了一个生成器函数,通过这个接口,每次取迭代获取参数,都将获得一个生成器,不用像前面那么复杂的去创建一个迭代器。

    生成器函数的工作原理

    只要Python函数的定义体中有yield关键字,该函数就是生成器函数。调用生成器函数,会返回一个生成器对象。也就是说,生成器函数是生成器工厂。

    以前总是错误的认为yield就是return的变相版本,其实这样理解是非常不合理的。

    生成器,从字面定义就是生产,产出了什么,是凭空多出来的。所以,每一次yield就是产出一个数据,产出以后当然还能继续产出,所以整个函数体并不会执行结束。

    这是一种惰性的特征。

    14.5sentence类第4版:惰性实现

    import re
    import reprlib
    
    RE_WORD = re.compile('w+')
    
    
    class Sentence:    # 定义成序列的协议,有__getitem__与__len__
    
        def __init__(self, text):
            self.text = text
    
        def __len__(self):
            return len(self.word)
    
        def __repr__(self):
            return f'{type(self).__name__}({reprlib.repr(self.text)})'
    
        def __iter__(self):    # 生成器函数
            for match in RE_WORD.finditer(self.text):   # 惰性查找,构建了一个迭代器。
                yield match.groups()          # 产出查找对象的内容   
                
    

    14.6 Sentence类第5版:生成器表达式

    In [57]: def gen_AB(): 
        ...:     print('start') 
        ...:     yield 'A' 
        ...:     print('continue') 
        ...:     yield 'B' 
        ...:     print('end') 
        ...:                                                                                             
    
    In [58]: res2 = (x*3 for x in gen_AB())                                                              
    
    In [59]: next(res2)                                                                                  
    start
    Out[59]: 'AAA'
    
    In [60]: next(res2)                                                                                  
    continue
    Out[60]: 'BBB'
    
    In [61]: next(res2)                                                                                  
    end
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-61-45c410d5a3dc> in <module>
    

     这是通过next调用生成器的执行结果。

    In [62]: def res2_gen(ob): 
        ...:     for i in ob: 
        ...:         yield i*3 
        ...:                                                                                             
    
    In [63]: res3= res2_gen(gen_AB())                                                                    
    
    In [64]: next(res3)                                                                                  
    start
    Out[64]: 'AAA'
    
    In [65]: for i in res3: 
        ...:     print(i) 
        ...:                                                                                             
    continue
    BBB
    end
    
    In [66]:     
    

     通过对比发现,生成器表达式就是一个简单的生成器函数的语法糖写法。

    生成器表达式的一个参数,是生成器函数yield产出的东西。

    如果说生成器函数为一个五脏俱全的生成器工厂,那生成器表达式就是一个简化版的生成器加工作坊。

    这个也可以说明,一些简单的逻辑的生成器函数可以用生成器表达式完成。

    import re
    import reprlib
    
    RE_WORD = re.compile('w+')
    
    
    class Sentence:  # 定义成序列的协议,有__getitem__与__len__
    
        def __init__(self, text):
            self.text = text
    
        def __len__(self):
            return len(self.word)
    
        def __repr__(self):
            return f'{type(self).__name__}({reprlib.repr(self.text)})'
    
        def __iter__(self):  # 返回一个生成器
            return (match.groups() for match in RE_WORD.finditer(self.text))
    

    14.8另一个示例:等差数列生成器

    class ArithmeticProgression:
        
        def __init__(self, begin, step, end=None):
            self.begin = begin
            self.step = step
            self.end = end
        
        def __iter__(self):
            result = type(self.begin + self.step)(self.begin)   # 初始化第一个数字,按照step的格式要求
            forever = self.end is None     # 判断有没有最后截止
            index = 0
            while forever or result < self.end:   # 如果forever成立就是无线取值,forever不成立,有限循环,最后的值<设置的end
                yield result
                index += 1
                result = self.bigin + self.step * index   # 选择这种方式累加,可以降低处理浮点数时候累积效应致错的风险
    

     运行的结果:

     [76]: ap = ArithmeticProgression(0,1,3)                                                           
    
    In [77]: list(ap)                                                                                    
    Out[77]: [0, 1, 2]
    
    In [78]: ap = ArithmeticProgression(0,1/3,3)                                                         
    
    In [79]: list(ap)                                                                                    
    Out[79]: 
    [0.0,
     0.3333333333333333,
     0.6666666666666666,
     1.0,
     1.3333333333333333,
     1.6666666666666665,
     2.0,
     2.333333333333333,
     2.6666666666666665]
    
    In [80]: from fractions import Fraction                                                              
    
    In [81]: ap = ArithmeticProgression(0,Fraction(1,3),1)                                               
    
    In [82]: list(ap)                                                                                    
    Out[82]: [Fraction(0, 1), Fraction(1, 3), Fraction(2, 3)]
    
    In [83]: from decimal import Decimal                                                                 
    
    
    In [85]: ap = ArithmeticProgression(0,Decimal('.1'),0.5)                                             
    
    In [86]: list(ap)                                                                                    
    Out[86]: [Decimal('0'), Decimal('0.1'), Decimal('0.2'), Decimal('0.3'), Decimal('0.4')]
    
    In [87]:  
    
                                                                                                         
    

     前面是通过类的方式,还需要实例化,然后通过iter得到迭代器,书中还有一个更加好的,用生成器函数,直接用函数返回一个生成器。

    def aritprog_gen(begin, step, end=None):
        result = type(begin+step)(begin)
        forever = end is None
        index = 0
        while forever or result<end:
            yield result
            index += 1
            result = begin + step * index
    

     这个直接调用这个函数,list或者for循环它都可以拿到它的元素

    In [88]: ap = aritprog_gen(0,Decimal('.1'),0.5)                                                      
    
    In [89]: list(ap)                                                                                    
    Out[89]: [Decimal('0'), Decimal('0.1'), Decimal('0.2'), Decimal('0.3'), Decimal('0.4')]
    
    In [90]:  
    

     运行的结果当然也是一样的。

    使用itertoos模块生成等差数列

    先介绍count

    In [99]: c = count(1,.5)                                                                             
    
    In [100]: next(c)                                                                                    
    Out[100]: 1
    
    In [103]: next(c)                                                                                    
    Out[103]: 1.5
    
    In [104]: next(c)                                                                                    
    Out[104]: 2.0
    
    In [105]: next(c)                                                                                    
    Out[105]: 2.5
    

     跟前面自己定义的步进器有点像,但收个数字格式不对。

    还有一个takewhile

    In [106]: from itertools import takewhile                                                            
    
    In [107]: takewhile?                                                                                 
    Init signature: takewhile(self, /, *args, **kwargs)
    Docstring:     
    takewhile(predicate, iterable) --> takewhile object
    
    Return successive entries from an iterable as long as the 
    predicate evaluates to true for each entry.
    Type:           type
    Subclasses:     
    
    In [108]:       
    

     从说明来看,前面放一个函数,只要函数返回是True的,那写元素可以用通过这个takewhile这个类输出。

    如果一旦有一个错误的,那后面的可迭代对象在这个错误体后面(包括这个错误体)不能再生产出元素。

    感觉说的很奇怪

    In [109]: take = takewhile(lambda x:x !='l','hello')                                                 
    
    In [110]: next(take)                                                                                 
    Out[110]: 'h'
    
    In [111]: next(take)                                                                                 
    Out[111]: 'e'
    
    In [112]: next(take)                                                                                 
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-112-f33df8f3fdf8> in <module>
    ----> 1 next(take)
    
    StopIteration: 
    
    In [113]:                                                                                            
    
    In [113]: next(take)                                                                                 
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-113-f33df8f3fdf8> in <module>
    ----> 1 next(take)
    
    StopIteration: 
    

     代码运行出来的结果就很好理解了。

    后面的hello,当碰到条件为flase时,就是说输出l的时候,迭代器结束工作,返回StopIteration。

    通过与前面的count结合,可以设置一个有限的输出步进迭代器。

    In [114]: sc = takewhile(lambda x: x<3,count(1,0.3))                                                 
    
    In [115]: list(sc)                                                                                   
    Out[115]: [1, 1.3, 1.6, 1.9000000000000001, 2.2, 2.5, 2.8]
    
    In [116]:  
    

     既然这样,我们就通过内置的一些模块,修改前面写的模块

    from itertools import count, takewhile
    
    def aritprog_gen(begin, step, end=None):
        first = type(begin+step)(begin)
        ap_gen = count(first, step)     # 默认是count
        if end is not None:        # 如果 end不为空
            ap_gen = takewhile(lambda x: x < end, ap_gen)    # 通过takewilhe重新限制生成器,并覆盖ap_gen
        return ap_gen         # 返回一个生成器
    
    In [117]: ari = aritprog_gen(1,1/2,4)                                                                
    
    In [118]: list(ari)                                                                                  
    Out[118]: [1.0, 1.5, 2.0, 2.5, 3.0, 3.5]
    
    In [119]:  
    

    14.9标准库中的生成器函数。

    独立开一篇新随笔:https://www.cnblogs.com/sidianok/p/12150599.html

    14.10Python3.3中新出现的语句:yield from

    In [284]: def chain(*iterable): 
         ...:     for it in iterable: 
         ...:         for i in it: 
         ...:             yield i 
         ...:                                                                                            
    
    In [285]: s = 'abc'                                                                                  
    
    In [286]: t = range(3)                                                                               
    
    In [287]: list(chain(s,t))                                                                           
    Out[287]: ['a', 'b', 'c', 0, 1, 2]
    
    In [288]: def chain(*iterable): 
         ...:     for it in iterable: 
         ...:         yield from it 
         ...:          
         ...:                                                                                            
    
    In [289]: list(chain(s,t))                                                                           
    Out[289]: ['a', 'b', 'c', 0, 1, 2]
    
    In [290]:  
    

     yield from i完全替代了内层的for循环,yield from还会创建通道,把内层生成器直接与外层生成器的客户端联系起来。

    14.11 可迭代的归约函数。

    接收一个可迭代的对象,然后返回单个结果,这些函数叫做"归约"函数、"合拢"函数、或"累加"函数。

    all,any,max,min,functools.reduce,sum,

    其中all,any函数会短路

    14.12 深入分析iter函数

    In [290]: iter?                                                                                      
    Docstring:
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator
    
    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.
    Type:      builtin_function_or_method
    

     还可以前面传入一个函数,后面传入一个哨兵,每次执行next,产出函数的值,如果函数产出的值不等于哨兵,就产出。

    In [291]: def demo(): 
         ...:     return random.randrange(5) 
         ...:                                                                                            
    
    In [292]: it = iter(demo,3)                                                                          
    
    In [293]: next(it)                                                                                   
    Out[293]: 0
    
    In [294]: next(it)                                                                                   
    Out[294]: 1
    
    In [295]: next(it)                                                                                   
    Out[295]: 1
    
    In [296]: next(it)                                                                                   
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-296-bc1ab118995a> in <module>
    ----> 1 next(it)
    
    StopIteration: 
    
    In [303]: it = iter(demo,3)                                                                          
    
    In [304]: for i in it: 
         ...:     print(i) 
         ...:                                                                                            
    2
    
  • 相关阅读:
    让.Net程序支持命令行启动
    拒绝卡顿——在WPF中使用多线程更新UI
    比NPOI更好用的Excel操作库——EPPlus
    利用Visual Studio Natvis 框架简化C++的变量调试工作
    使用LibZ合并.Net程序集,支持WPF
    SONY新的圈铁耳机
    找回VisualStudio异常设置中丢失的“用户未处理的(User-unhandled)”列
    去除下载文件属性中烦人的锁定状态
    POJ 3347 Kadj Squares
    POJ 1696 Space Ant(极角排序)
  • 原文地址:https://www.cnblogs.com/sidianok/p/12147577.html
Copyright © 2011-2022 走看看