深入了解迭代器

实例:实现一个以深度优先方式遍历树形节点的生成器

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):
        yield self  #返回节点
        for c in self:#遍历子节点
            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)

yield之前已经讲过了,这里钟点介绍yield from原理

yield from原理:

yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器;返回迭代器

举例,将

a= [1,2,3,4,4]
b =['a','b','c']
d ={'key':"people","value":"lian"}拼接成一个列表

方法一:

a= [1,2,3,4,4]
b =['a','b','c']
d ={'key':"people","value":"lian"}

def yieldtest(*args):
    for item in args:
        for i in item:
            yield  i
lis =yieldtest(a,b,d)
print(list(lis))

方法二:yield from

def yieldfrom(*args):
    for item in args:
        yield from item


a= [1,2,3,4,4]
b =['a','b','c']
d ={'key':"people","value":"lian"}
lis =yieldfrom(a,b,d)
print(list(lis))

综上可知,使用yield from更加简洁,另外需要声明的是,yield from它可以帮我们处理异常,我们不需要自己捕获异常并处理

具体yield from为我们做了哪些事,可以参考如下这段代码:

#一些说明
"""
_i:子生成器,同时也是一个迭代器
_y:子生成器生产的值
_r:yield from 表达式最终的值
_s:调用方通过send()发送的值
_e:异常对象
"""

_i = iter(EXPR)

try:
    _y = next(_i)
except StopIteration as _e:
    _r = _e.value

else:
    while 1:
        try:
            _s = yield _y
        except GeneratorExit as _e:
            try:
                _m = _i.close
            except AttributeError:
                pass
            else:
                _m()
            raise _e
        except BaseException as _e:
            _x = sys.exc_info()
            try:
                _m = _i.throw
            except AttributeError:
                raise _e
            else:
                try:
                    _y = _m(*_x)
                except StopIteration as _e:
                    _r = _e.value
                    break
        else:
            try:
                if _s is None:
                    _y = next(_i)
                else:
                    _y = _i.send(_s)
            except StopIteration as _e:
                _r = _e.value
                break
RESULT = _r

 

重点:

认识yield from需要了解以下几个概念

1、调用方:调用委派生成器的客户端(调用方)代码
2、委托生成器:包含yield from表达式的生成器函数
3、子生成器:yield from后面加的生成器函数

举例:实现实时计算平均值

比如,第一次传入10,那返回平均数自然是10.
第二次传入20,那返回平均数是(10+20)/2=15
第三次传入30,那返回平均数(10+20+30)/3=20

# 子生成器

def average_gen():
    count = 0
    total = 0
    average = 0
    t = 0
    while 1:
        new_num = yield average
        count += 1
        total += new_num
        average = total / count  # 返回average


# 委托生成器

def proxy_gen():
    while 1:
        yield from average_gen()


# 调用方


def main():
    gen = proxy_gen()
    next(gen)
    print(gen.send(10))
    print(gen.send(20))
    print(gen.send(30))


if __name__ == "__main__":
    main()

委托生成器的作用是:在调用方与子生成器之间建立一个双向通道。
所谓的双向通道是什么意思呢?
调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。

前面我们讲过使用reversed来实现链表逆序,如

l =[1,2,3,4]
    for i in reversed(l):
        print(i)

反向迭代仅仅当对象的大小可预先确定或者对象实现了 __reversed__() 的特殊方法时才能生效。 如果两者都不符合,那你必须先将对象转换为一个列表才行,数据量少还可以,但是一旦数据量上去了,必会占用大量内存,在这里给大家介绍一个鲜有人知的点,可以通过自定义__reversed__来节约内存,可以使得代码非常的高效, 因为它不再需要将数据填充到一个列表中然后再去反向迭代这个列表。

class Countdown:
    def __init__(self, start):
        self.start = start

    # Forward iterator
    def __iter__(self):
        pass
        

    # Reverse iterator
    def __reversed__(self):
        n = 1
        while n <= self.start:
            yield n
            n += 1

for rr in reversed(Countdown(30)):
    print(rr)

迭代器切片:函数 itertools.islice() 正好适用于在迭代器和生成器上做切片操作。比如:

# 子生成器
from itertools import islice

if __name__ == "__main__":
    
    l = (i for i in range(1000))
    # islice(iterable, stop) --> isliceobject
    # islice(iterable, start, stop[, step]) --> isliceobject
    for i in islice(l,1,5,2):
        print(i)

跳过可迭代对象的开始部分itertools.dropwhile() 函数。使用时,你给它传递一个函数对象和一个可迭代对象。 它会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素。

from itertools import islice, dropwhile

if __name__ == "__main__":

    # dropwhile(predicate, iterable) --> dropwhileobject
    l = iter(('#hhe', "emm", "#ss", "map"))

    for i in dropwhile(lambda i: i.startswith('#'), l):

        print(i)

 

排列组合的迭代itertools.permutations() ,它接受一个集合并产生一个元组序列, 通过打乱集合中元素排列顺序生成一个元组,第一个参数接受容器,第二个可选的长度参数, 比如:

from itertools import islice, dropwhile,permutations

if __name__ == "__main__":

    l = [1,2,3]
    for i in permutations(l,2):
        print(i)

(1, 2)
(1, 3)
(2, 1)
(2, 3)
(3, 1)
(3, 2)

itertools.combinations()和itertools.permutations效果差不多,但是itertools.combinations不考虑顺序,在计算组合的时候,一旦元素被选取就会从候选中剔除掉,而 itertools.combinations_with_replacement() 允许同一个元素被选择多次

# 子生成器
from itertools import islice, dropwhile,permutations,combinations,combinations_with_replacement

if __name__ == "__main__":

    l = [1,2,3]
    for i in  combinations(l,3):
        print(i)
    for i in  combinations(l,2):
        print(i)
    print("-------")
    for i in  combinations_with_replacement(l,3):
        print(i)
    for i in  combinations_with_replacement(l,2):
        print(i)

序列上索引值迭代:使用内置的 enumerate() 函数

if __name__ == "__main__":

    l = ['one','two','three']
    for i,j in enumerate(l,2):
        print(i,j)

第二个参数表示从行号2开始算起,默认1

同时迭代多个序列:zip(a, b) 会生成一个可返回元组 (x, y) 的迭代器,其中x来自a,y来自b。 一旦其中某个序列到底结尾,迭代宣告结束。 因此迭代长度跟参数中最短序列长度一致。如果这个不是你想要的效果,那么还可以使用 itertools.zip_longest() 函数来代替

if __name__ == "__main__":

    l = ['one','two','three']
    ll = [1,2,3]
    lll=[4,6,7]
    for i,j,k in zip(l,ll,lll):
        print(i,j,k)


output:one 1 4
two 2 6
three 3 7

from  itertools import zip_longest
if __name__ == "__main__":

    l = ['one','two','three']
    ll = [1,2,3]

    lll=[4,6,7,9]
    for i,j,k in zip_longest(l,ll,lll):
        print(i,j,k)
output:
one 1 4
two 2 6
three 3 7
None None 9

不同集合上元素的迭代:在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环,itertools.chain() 方法可以用来简化这个任务。 它接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节。 

from  itertools import zip_longest,chain
if __name__ == "__main__":

    l = ['one','two','three']
    ll = [1,2,3]

    lll=[4,6,7,9]
    for i in chain(l,ll):
        print(i)

 顺序迭代合并后的排序迭代对象:你有一系列排序序列,想将它们合并后得到一个排序序列并在上面迭代遍历

import  heapq
if __name__ == "__main__":

    ll = [1,2,9,10,3]

    lll=[4,6,7]
    for i in heapq.merge(ll,lll):
        print(i)

iter 函数一个鲜为人知的特性是它接受一个可选的 callable 对象和一个标记(结尾)值作为输入参数。 当以这种方式使用的时候,它会创建一个迭代器, 这个迭代器会不断调用 callable 对象直到返回值和标记值相等为止。

这种特殊的方法对于一些特定的会被重复调用的函数很有效果,比如涉及到I/O调用的函数。 举例来讲,如果你想从套接字或文件中以数据块的方式读取数据,通常你得要不断重复的执行 read()或 recv() , 并在后面紧跟一个文件结尾测试来决定是否终止。这节中的方案使用一个简单的 iter() 调用就可以将两者结合起来了。 其中 lambda 函数参数是为了创建一个无参的 callable对象,并为 recv 或 read() 方法提供了 size 参数。

import sys
f = open('/etc/passwd')
for chunk in iter(lambda: f.read(10), ''):
     n = sys.stdout.write(chunk)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值