流畅的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
原文地址:https://www.cnblogs.com/sidianok/p/12147577.html