zoukankan      html  css  js  c++  java
  • PythonCookbook第4章(迭代器和生成器)良好完成

    迭代使Python中最强有力的特性之一。从高层次看,我们可以简单地把迭代看做是一个处理序列中元素的方式。

    4.1手动访问迭代器中的元素

    from collections import abc
    
    with open('/etc/passwd') as f:
        print(isinstance(f, abc.Iterator))
        print(isinstance(f, abc.Generator))
        try:
            while True:
                line = next(f)
                print(line, end='')
        except StopIteration:
            ...
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_4/t1_2.py
    True
    False
    ##
    # User Database
    

     open对象是一个迭代器对象。

    讨论:

    内容太少,不写了

    4.2 委托代理

    问题

    我们构建一个自定义的容器对象,对内部持有一个列表、元祖或者其他的可迭代对象。我们想让自己的新容器完成迭代操作。

    解决:

    迭代器需要有__iter__与__next__属性,通过iter函数执行返回一个迭代器是一个比较简单的好方法。

    class Node:
    
        def __init__(self, value):
            self._value = value
            self._children = []
    
        def __repr__(self):
            return 'Node({!r})'.format(self._value)
    
        def add_child(self, node):
            self._children.append(node)
    
        # for循环首先调用对象的该方法,然后通过__next__方法读取参数,读取完,返回信息StopIteration
        def __iter__(self):
            return iter(self._children)
    
    if __name__ == '__main__':
        root = Node(0)
        child1 = Node(1)
        child2 = Node(2)
        root.add_child(child1)
        root.add_child(child2)
        for ch in root:
            print(ch)
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_4/t2_2.py
    Node(1)
    Node(2)
    
    Process finished with exit code 0
    

     讨论:

    Python的迭代协议要求__iter__()返回一个特殊的迭代器对象,由该对象实现的__next__()方法来完成实际的迭代。

    4.3 用生成器创建新的迭代模式

    问题:

    我们创建一个自己的迭代模式,使其区别于常见的内建函数(range(),recersed())

    解决方案

    创建一个生成器函数

    def frange(start, stop, step):
        x = start
        while x < stop:
            yield x
            x += step
    
    
    if __name__ == '__main__':
        for n in frange(10, 12, 0.5):
            print(n)
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/PythonCookbook/chapter_4/t3_2.py
    10
    10.5
    11.0
    11.5
    
    Process finished with exit code 0
    

     讨论:

    In [90]: def countdown(n): 
        ...:     print('Starting go count from', n) 
        ...:     while n > 0: 
        ...:         yield n 
        ...:         n -= 1 
        ...:     print('Dnne') 
        ...:                                                                                                                       
    
    In [91]: c= countdown(3)                                                                                                       
    
    In [92]: # 生成器函数运行的时候不执行                                                                                          
    
    In [93]: next(c)                                                                                                               
    Starting go count from 3
    Out[93]: 3
    
    In [94]: # 第一次执行到yield n处                                                                                               
    
    In [95]: next(c)                                                                                                               
    Out[95]: 2
    
    In [96]: # 程序向下执行寻找下一个yield处                                                                                       
    
    In [97]: next(c)                                                                                                               
    Out[97]: 1
    
    In [98]: next(c)                                                                                                               
    Dnne
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-98-e846efec376d> in <module>
    ----> 1 next(c)
    
    StopIteration: 
    
    In [99]: # 当next向下执行未能找到yiled,函数返回,迭代结束。  
    

    函数中只要出现了yield语句就会将其转变成一个生成器。与普通函数不同,生成器只会在响应迭代操作才运作。

    生成器函数只会在响应迭代过程中的"next"操作时才会运行。一旦生成器函数返回,迭代就停止了

    4.4 实现迭代协议

    问题:创建一个对象,希望他可以支持迭代操作,但是也希望能有一种简单的方式来实现迭代。

    解决方案:

    主要实现了一个可以以深度优先的模式遍历树的节点。

    class Node:
        def __init__(self, value):
            self._value = value
            self._children = []
    
        def __repr__(self):
            return 'Node({!r})'.format(self._value)
    
        def add_child(self, node):
            self._children.append(node)
    
        def __iter__(self):
            return iter(self._children)
    
        def depth_first(self):
            # 首先返回自己,然后循环读取自己,for循环调用__iter__返回子生的迭代器内的内容
            # 再词调用对象的depth_first方法进行递归。其实用双层的for循环比用yield form好理解一些
            # yield form当用作委派生成器的时候用更加好。
            yield self
            for c in self:
                for i in c.depth_first():
                    yield i
                # 效果等同于上面的for循环,前面的yield from快忘光了
                # yield from c.depth_first()
    
    
    if __name__ == '__main__':
        root = Node(0)
        child1 = Node(1)
        child2 = Node(2)
        root.add_child(child1)
        root.add_child(child2)
        child1.add_child(Node(3))
        child1.add_child(Node(4))
        child2.add_child(Node(5))
    
        for ch in root.depth_first():
            print(ch)
    
    /usr/local/bin/python3.7 /Users/shijianzhong/study/Fluent_Python/第十六章/coroaverager3.py
     6 boys  averaging 54.00kg
     6 boys  averaging 1.68m
     6 girls averaging 44.00kg
     6 girls averaging 1.58m
    
    Process finished with exit code 0
    

     讨论:

    书中还写以一种关联迭代器的写法,代码比较复杂,上面的用递归的方式,深度优先遍历对象,我对递归还是比较抵触的。

    书中的代码还是上一下算了,写法可能复杂点,看能不能理解起来更加简单。

    class Node:
        def __init__(self, value):
            self._value = value
            self._children = []
    
        def __repr__(self):
            return 'Node({!r})'.format(self._value)
    
        def add_child(self, node):
            self._children.append(node)
    
        def __iter__(self):
            return iter(self._children)
    
        def depth_first(self):
            return DepthFirstIterator(self)
    
    class DepthFirstIterator:
    
        def __init__(self, start_node):
            self._node = start_node
            self._children_iter = None
            self._child_iter = None
    
        def __iter__(self):
            return self
    
        # 关键在与__next__具体的执行
        def __next__(self):
            # 第一次肯定执行这个,返回初始化的DepthFirstIterator里的self,也就是Node实例
            if self._children_iter is None:
                self._children_iter = iter(self._node)
                return self._node
            # 这个是第三步操作,从第二步的return那里过来
            elif self._child_iter:
                try:
                    nextchild = next(self._child_iter)
                    return nextchild
                except StopIteration:
                    self._child_iter = None
                    return next(self)
            # 第二步,从self._children_iter取出一个对象,执行depth_first方法
            else:
                # 执行这一步的时候已经产生了递归了,看的眼睛都涨死了
                self._child_iter = next(self._children_iter).depth_first()
                # 对自己进行下一步操作
                return next(self)
    
    if __name__ == '__main__':
        root = Node(0)
        child1 = Node(1)
        child2 = Node(2)
        root.add_child(child1)
        root.add_child(child2)
        c_child3 = Node(3)
        c_child3.add_child(Node(88))
        child1.add_child(c_child3)
        child1.add_child(Node(4))
        child2.add_child(Node(5))
    
        for ch in root.depth_first():
            print(ch)
    

     第二种方法,代码更加复杂,而且从理解来看,一点也没的方便。

    4.5 反向迭代

    问题:

    反向迭代序列中的元素

    解决方案:

    reversed函数实现

    反向迭代只有待处理的对象拥有可确定的大小,或者对象实现了__reveresd__()方法时才能奏效。如果两个条件都无法满足,先转换为列表。

    也就是说,不能对迭代器直接使用reversed函数。

    讨论:

    可以定义__reversed__方法的对象,给自己使用。

    class Countdown:
        def __init__(self, start):
            self.start = start
        
        # 从开始的数字开始
        def __iter__(self):
            n = self.start
            while n > 0:
                yield n
                n += 1
        
        # 从1开始到初始化的数字结束
        def __reversed__(self):
            n = 1
            while n <= self.start:
                yield n
                n += 1
    
    if __name__ == '__main__':
        c =Countdown(10)
        forward = iter(c)
        reveres = reversed(c)
        print(next(forward))
        print(next(reveres))
    

    4.6 定义带有额外状态的生成器函数

    问题:

    定义一个生成器函数,但是它涉及一些额外的状态,需要显示

    解决方案:

    定义一个类,需要显示的东西放在实例属性里面,生成器函数定义在__iter__里面,输出前几行的内容,很好的一个案例

    from collections import deque
    
    
    class Line_History:
        def __init__(self, lines, histlen=3):
            self.lines = lines,
            self.history = deque(maxlen=histlen)
        
        def __iter__(self):
            for lineno, line in enumerate(self.lines, start=1):
                # 读取行号与内容放去双端队列里面去
                self.history.append((lineno, line))
                yield line
        
        def clear(self):
            self.history.clear()
            
    if __name__ == '__main__':
        
        with open('sometext') as f:
            # f是一个迭代器,我测试过
            lines = Line_History(f)
            # 调用iter方法
            for line in lines:
                if 'python' in line:
                    # 输出对象属性的双端队列里面的内容
                    for lineno, hline in lines.history:
                        print('{}:{}'.format(lineno, hline), end='')
    

    讨论:

    就是解决方案里面的第一句话

    4.7对迭代器做切片操作

    问题:

    相对迭代器进行切片

    解决方案:

    itertools.islice函数

    讨论:

    islice是通过访问并丢弃所有起始索引之前的元素来实现的,之后的元素有islice对象产出。

    操作islice对象的时候,其实就是在操作原来的对象,如果还需要倒回访问前面的数据,就应该先将数据转到列表中去。

    4.8跳过可迭代对象中的前一部分元素

    问题:

    对一个可迭代对象处理,但对前面的几个元素不感兴趣,想把它们丢弃

    解决方案:

    itertools.dropwhile函数可以实现。

    In [72]: from itertools import dropwhile                                                                                                 
    
    In [73]: dropwhile?                                                                                                                      
    Init signature: dropwhile(self, /, *args, **kwargs)
    Docstring:     
    dropwhile(predicate, iterable) --> dropwhile object
    
    Drop items from the iterable while predicate(item) is true.
    Afterwards, return every element until the iterable is exhausted.
    Type:           type
    Subclasses:     

    第一个参数为函数,第二个参数为操作的迭代对象。

    当满足函数的条件时,这些可迭代对象里面的元素将被忽略,直到出现不满足条件的元素出现,包含这个不满足条件的元素以及后面的元素都将不被筛选直接返回。

    讨论:

    dropwhile与islice都是很好用的函数,以后要记得。

    4.9迭代所有的组合或排列

    专门写过

    combinations组合,不关心元素的位置

    permutations排列,考虑元素的位置不同

    4.10 以索引-值对的形式迭代序列

    问题:

    想处理一个序列,但是想记录下序列中当前处理到的元素索引。

    解决方案:

    enumerate是一个很好的函数,对于追踪文本操作还是非常有用的。上一个书中的经典代码

    from collections import defaultdict
    
    # 创建一个默认字典
    word_summary = defaultdict(list)
    
    with open('myfile.txt') as f:
        # 按行读取所有的内容
        lines = f.readlines()
    
    for idx, line in enumerate(lines):
        # 每个单词变小写,前后取出空格
        words = [w.strip().lower() for w in line.split()]
        # 读取单词放入,用单词做key将每一行放入value
        for word in words:
            word_summary[word].append(idx)
    

     讨论:

    enumerate()的返回值是一个enumerate对象实例,它是一个迭代器,可返回连续的元祖。元祖有一个索引值和对传入的序列iter以后调用next()而得到的值组成。

    4.11 同时迭代多个序列

    zip与itertools.zip_longest

    前期已经详细介绍,略。

    4.12 在不同的容器中进行迭代

    问题:

    需要对许多对象执行相同的操作,但是这些对象包含在不同的容器内,为了避免写出嵌套的循环处理,保持代码的可读性

    解决方案:

    itertools.chain

    讨论:

    for x in a+b:
    
      ...
    
    for x in chain(a, b):
    
      ...
    

    第一种方式,需要a与b为同一种类型,而且会产生一个全新的序列,第二种方式的内存使用要小,因为chain返回的是一个迭代器

    4.13 创建处理数据管道

    问题:

    我们要处理海量的数据,但是没办法将数据全部加载到内存中去

    解决方案:

    通过定义一系列小型的生成器函数,每个函数执行特定的独立任务

    import os
    import fnmatch
    import gzip
    import bz2
    import re
    
    def gen_find(filepat, top):
        '''
        找出需要处理的文件的绝对路径
        '''
        for path, dirlist, filelist in os.walk(top):
            # 取出指定文件名的文件
            for name in fnmatch.filter(filelist, filepat):
                yield os.path.join(path, name)
    
    
    def gen_opener(filenames):
        # 打开文件,产出文件内容流
        for filename in filenames:
            if filename.endswith('.gz'):
                f = gzip.open(filename, 'rt')
            elif filename.endswith('.bz2'):
                f = bz2.open(filename, 'rt')
            else:
                f = open(filename, 'rt')
            yield f
            f.close()
    
    def gen_concatenate(iterators):
        # for循环接收上一个生成器出来的文件流对象,通过yield from产出每一个行
        for it in iterators:
            yield from it
    
    def gen_grep(pattern,lines):
        pat = re.compile(pattern)
        for line in lines:
            print(line)
            if pat.search(line):
                yield line
    
    if __name__ == '__main__':
        lognames = gen_find('1.txt','../chapter_4')
        files = gen_opener(lognames)
        lines = gen_concatenate(files)
        pylines = gen_grep('(.*)',lines)
        for line in pylines:
            print(line)
    

     讨论:

    通过前面的代码,我们可以分析看到,yield变现为数据的生产者,for表现为数据的消费者

    每一个生成器函数里面,都有for循环来接收上一级的产出,然后通过yield语句为下一个生成器函数产出数据。

    当最终的执行的时候,会调用每一层的生成器函数,这个感觉就想变速箱的齿轮,一个转动,每个都开始动起来了。

    这里面最关键的是for循环接收上一次生成器函数的产出数据,和自身yield产出数据给下一层使用者。

    4.14扁平化处理嵌套型的序列

    问题:

    一个嵌套的序列,需要扁平化处理一列单独的值

    解决方案:

    主要是通过递归的方式处理,书中还用了yield from

    from collections.abc import Iterable
    
    def flatten(items, ignore_types=(str, bytes)):
        for x in items:
            # 如果是迭代的对象并且不是字符串,字节码
            if isinstance(x, Iterable) and not isinstance(x, ignore_types):
                # 进入递归
                # yield from flatten(x)
                # 第二种写法
                for i in flatten(x):
                    yield i
            else:
                yield x
    
    
    items = [1, 2, [3,[4,]],5]
    
    if __name__ == '__main__':
        print(list(flatten(items)))
    

     讨论:

    内容已经在代码里的,yield from在流畅的Python中有着更加详细的介绍,书中的写法只不过用到了其中的一点点功能,但对于递归,我是真的头痛,想多了脑子就想炸了。

    从这里简单的用法来看,yield from会将一个可迭代对象变成迭代器,并产出迭代器内部的值(这里用到的功能)。

    4.15 合并多个有序序列,再对整个有序序列进行迭代

    问题:

    有两个有序的序列,相对它们合并在一起之后的有序序列进行迭代

    解决方案:

    heapq.merge函数实现,返回一个生成器,非常节省内存

    In [1]: import heapq                                                                                                          
    
    In [2]: l1 = [1,2,3,4,]                                                                                                       
    
    In [3]: l2 = [3,4,5,6]                                                                                                        
    
    In [4]: l3 = heapq.merge(l1,l2)                                                                                               
    
    In [5]: l3                                                                                                                    
    Out[5]: <generator object merge at 0x1060af5d0>
    
    In [6]: list(l3)                                                                                                              
    Out[6]: [1, 2, 3, 3, 4, 4, 5, 6]
    

     讨论:

    heapq.merge的迭代性质意味着它对所有提供的序列不会做一次性读取。这意味着可以利用它处理非常长的序列,而开销非常小。

    heapq.mergr要求输入的序列都是有序的。

    4.16用迭代器取代while循环

    问题:

    我们的代码采用while循环来迭代处理数据,因为这其中涉及调用某个函数或有某种不常见的测试条件,而这些东西没法归类为常见的迭代模式。

    解决方法:

    iter(func,condition),通过iter的哨兵作为停止条件。

    import random
    CHUNKSIZE = 8192
    
    
    def reader(s):
        while True:
            data = s.recv(CHUNKSIZE)
            if data == b'':
                break
            process_data(data)
    
    
    # 用iter写
    def reader(s):
        for chuck in iter(lambda :s.recv(CHUNKSIZE), b''):
            process_data(chuck)
    
    
    if __name__ == '__main__':
        # 自己回忆了一下,以前流畅的Python中的案例。
        for i in iter(lambda :random.randint(1,10),8):
            print(i)
    

     讨论:

    iter()可以接收一个无参的可调用对象以及一个哨兵(结束)值作为输入。当以这种方式使用时,iter()会创建一个迭代器,然后重复调用用户提供的可调用对象,直到它返回哨兵值为止。

    这个在涉及到I/O的问题有很好的效果。

  • 相关阅读:
    IOC和工厂模式联合使用简化工厂模式
    免安装解压版mysql瘦身
    MYPM 国产非开源免费测试管理工具软件 WEB2.0用户体验零配置安装版本发布
    巧用Junit 静态变量
    动态加载JS和CSS
    浅谈测试管理工具对新人的潜移默化
    Pidgin——我用的环保QQ版本。无需安装解压即可运行。送上我本人写的菜鸟教材。
    我有一个梦想:WM手机商城创意。有初步的整体结构设计包括软硬件、服务器、客户端
    Form.close与Application.Exit()的区别
    ASP.NET 使用CustomValidator调用js函数动态修改验证TextBox的正则表达式,无刷新
  • 原文地址:https://www.cnblogs.com/sidianok/p/12329165.html
Copyright © 2011-2022 走看看