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的问题有很好的效果。

posted @ 2020-02-18 22:33  就是想学习  阅读(257)  评论(0编辑  收藏  举报