Py3——迭代器与生成器

http://python3-cookbook.readthedocs.io

迭代器

在讨论迭代器之前,我们先要了解什么是迭代以及什么是可迭代对象?

迭代:就是一个重复反馈过程的活动,简单的说就是一次返回一个结果。
可迭代对象(Iterable):在python里面来说,可迭代的对象有很多,比如 string,list,tuple等。当然生成器(generator)也是。
他们都有一个共同的特征,就是可以作用于for循环。

>>> str = "hello"
>>> for s in str:
...     print (s)
...
'h'
'e'
'l'
'l'
'o'

对于一个对象是否是 Iterable 对象可以用 isinstance() 函数来判断。
关于isinstance的作用,用法以及为什么用。知识点传送门:https://www.jianshu.com/p/7ef549503c93

>>> from collections import Iterable
>>> isinstance([],Iterable)
True
>>> isinstance((x for x in range(10)),Iterable)
True
>>> isinstance(list,Iterable)
True

迭代器(Iterator):可以被 next() 函数调用并不断返回下一个值的对象叫做迭代器

说到这里,我们要了解一个事情:所有的迭代器都是可迭代对象,而可迭代对象不一定是迭代器

>>> str = 'hello python !'
>>> isinstance(str,Iterable)
True
>>> next(str)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: str object is not an iterator
# string 对象是一个可迭代对象,但却不是一个迭代器!
# 在这里有一个iter方法可以使可迭代对象转换成迭代器
>>> next(iter(iter))
'h'

在实际使用时要将上面的str重新赋值 iter(str)

手动遍历迭代器

想遍历一个可迭代对象中的所有元素,但却不想使用 for 循环。

使用 next() 方法有个坑值得注意:当已经迭代到最后一个元素时再使用 next() 会直接报错

>>> s = iter([1,2])
>>> next(s)
1
>>> next(s)
2
>>> next(s)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

了解了上面的一个坑,我们就可以放心的写我们的代码,并且填坑了

>>> s = iter([1,2,3])
>>> while True:
...     line = next(s,None)
...     if line is None:
...             break
...     print (line)
... 
1
2
3

代理迭代

如果自定义了一个容器对象,包含有列表元组或其他可迭代对象,想直接在这个容器对象上执行迭代操作。

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)

    if __name__ == '__main__':
        root = Node(0)
        child1 = Node(1)
        child2 = Node(2)
        root.add_child(child1)
        root.add_child(child2)

        for c in root:
            print (c)

上面的iter() 方法返回一个实现了next() 方法的迭代器对象。功能和 iter() 是一样的只是写法上的不同, iter() 其实是一种简写形式,是通过 s.iter() 来实现的

生成器

生成器(generator):一边循环一边计算的机制叫做生成器
!!!生成器都是迭代对象(iterator)
相信大部分同学都使用过列表生成式,毕竟这是一个创建含多条数据的列表的简便方法。

>>> l = [x for x in range(10)]
>>> l
[0,1,2,3,4,5,6,7,8,9]

如果是十几条数据还好,如果是成千上万条数据那可就麻烦了,或许只访问了前几条数据,这样不仅造成了资源浪费还可能会受到内存限制。创建一个生成器是一个很好的方法

>>> l = (x for x in range(10))
>>> next(l)
0
>>> next(l)
1
...

创建一个 generator 后,最常用的是使用for循环来迭代它,并且迭代结束时不会报错。
想要更自由的创建一个 generator ,最好的方法是写一个与 yield 相配合的循环函数,以斐波拉契数列为例:

>>> def fblq(num):
...     n,a,b = 0,0,1
...     while n < num:
...         yield b
...         a,b = b,a+b
...         n = n + 1
...     return 'done'   
...
>>> f = fblq(4)
>>> f
<generator object fib at 0x104feaaa0>

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

在以上的内容中,我们了解了迭代,可迭代对象,迭代器以及生成器的概念。接下来我们就可以根据这些知识来自定义一个像 range() 这样的迭代模式。

>>> def frange(start,stop,increment):
...     x = start
...     while x < stop:
...         yield x
...         x += increment      

这样就创建了一个类似于 range() 的方法,不同的是可以接受浮点数作为步长。

反向迭代

希望反向迭代一个对象

>>> a = [1,2,3,4]
>>> for x in reversed(a):
...     print(x)
...
4
3
2
1

反向迭代只有当对象大小可预先确定或者是对象实现了reversed() 的特殊方法时才能生效,如果两者都不符合,必须先将对象转换成一个列表才行,而转换为列表是要消耗大量内存的需要注意

在自定义类上实现 reversed() 方法来实现反向迭代

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

    def __iter__(self):
        n = self.start
        while n>0:
            yield n
            n -= 1

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

for r in reversed(Countdown(30)):
    print(r)
for r in Cpuntdown(30):
    print(r)

迭代器切片

习惯了切片操作的简单粗暴高效,无论是列表,字符串还是元组,切片用起来总是得心应手。不能进行切片的的生成器还有什么值得留恋

标准切片操作是无法做到对生成器或者迭代器对象操作的,函数 itertools.islice() 是一个好东西

>>> def count(n):
...     while True:
...         yield n
...         n += 1

>>> c = count(0)
>>> import itertools
>>> x = itertools.islice(c,10,14)
# x 是一个生成器片段
>>> for i in x:
...     print i
...
10
11
12
13

关于 islice() 不得不了解的一些事,它通过遍历并丢弃直到切片开始索引位置的所有元素,然后才开始一个个的返回元素,直到切片结束索引位置。并且 islice() 会消耗掉传入的迭代器中的数据,所以你必须要考虑到迭代器是不可逆的这个事实
itertools模块中还有一个 dropwhile() 函数可以跳过可迭代对象的开始部分:

在使用 dropwhile() 函数时,需要传递一个函数对象和一个可迭代对象。他会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素。

如果是已经知道了要跳过的元素的个数的话,那么可以使用 itertools.islice() 来代替

>>> from itertools import islice
>>> items = ['a','b','c',1,4,10,15]
>>> for x in islice(items,3,None):
...     print(x)
...
1
4
10
15

关于上面例子参数 3 和 None 是指切片的起始位置

排列组合的迭代

迭代遍历一个集合中的所有可能的排列或组合

itertools.permutations() :它接受一个集合并产生一个元组序列,每个元组由集合中所有元素的一个可能排列组成。

 >>> items = ['a','b','c']
 >>> from itertools import permutations
 >>> all = permutations(items)
 >>> next(all)
 ('a','b','c')
 >>> next(all)
 ('a','c','b')
 >>> all2 = permutations(items,2)
 >>> next(all2)
 ('a','b') 

itertools.combinations() :它与 itertools.permutations() 的功能和用法类似,唯一不同的是 itertools.permutations() 生成的组合中不忽略组合中元素的顺序,而 itertools.combinations() 则忽略 组合中元素的次序,也就是说在 combinations() 方法中(‘a’,’b’) 与 (‘b’,’a’) 是相同的,只会输出一个。
itertools.combinations_with_replacement(): 它的功能和用法也类似第一个方法,不过常规的组合排列挑选一个元素后便排除再次挑选这个元素的可能,而此方法没有这个限制。

序列上索引值迭代

迭代一个序列的同时跟踪正在被处理的元素索引。

>>> l = ['a','b','c']
>>> for k,v in enumerate(l):
...     print(k,v)
...
0 a
1 b
2 c

也可以修改索引的起始值

>>> for k,v in enumerate(l,1):
...     print(k,v)
...
1 a
2 b
3 c

这个功能在遍历文件是定位错误信息行号是非常有用的
有个值得注意的一点
当解压了一个元组序列后,要注意一个小陷阱

data = [(1,2),(3,4),(5,6)]
#error 
for n,x,y in enumerate(data):
#correct
for n,(x,y) in enumerate(data):

同时迭代多个序列

同时迭代多个序列,每次分别从一个序列中取一个元素。

同时迭代多个序列的话,可以使用 zip() 函数。

>>> a = [1,5,4]
>>> b = [101,78,37]
>>> for x,y in zip(a,b):
...     print(x,y)
...
1 101
5 78
4 37

zip() 是会生成一个可返回元组的迭代器。一旦其中某个序列到底结尾,迭代宣告结束,所以迭代的长度和草书中最短序列长度一致。

如果你觉得这个效果并不能满足你的要求,那么可以使用 itertools.zip_longest() 函数来代替:
这道题在以前面试时遇见过,因为并不知道有这个方法,而自己技术浅薄一时之间没有写出替代方法,突然遇见,感慨万千!

'''我曾经遇见的面试题和这差不多,原理上都一样,只不过更复杂了点。
    大致要求就是迭代长度要和参数中最长序列保持一致,不足的部分用default替代,
    而且添加了许多限制导致很多实现方法不能符合要求'''  
>>> from itertools import zip_longest
>>> a = ['a','b']
>>> b = [1,2,3]
>>> for i in zip_longest(a,b):
...     print(i)
...
('a',1)
('b',2)
(None,3)
>>>
>>>
>>> for i in zip_longest(a,b,fillvalue='default'):
...     print(i)
...
('a',1)
('b',2)
('default',3)

zip() 的一些其他用途

1.假设有两个列表,要建立一个以 a 列表元素为键,以 b 列表元素为值的字典:
d = dict(zip(a,b))
2进行某些格式化操作,如将a,b两个列表元素使用 ‘=’ 一一对应输出
for k,v in zip(a,b):
print(k,”=”,v)

不同集合上元素的迭代

对不同容器中的多个对象执行相同的操作

itertools.chain() 方法可以用来简化这个任务,它接受一个或多个可迭代对象作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节

>>> from itertools import chain
>>> a = [1, 2]
>>> b = ('x', 'y', 'z')
>>> for x in chain(a,b):
...      print(x)
...      
1
2
x
y
z

展开嵌套的序列

想将一个多层嵌套的序列展开成一个单层列表

from collections 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)
        else:
            yield x

items = [1,2,[3,4,[5,6],7],8]
for x in flatten(items):
    print(x)

上面的例子,当判断某个元素是可迭代的,yield from 就会返回所有子例程的值

顺序迭代合并后的排序迭代对象

有一系列排序序列,将它们合并成一个排序序列并在上面迭代遍历

>>> import heapq
>>> a = [1,4,7,10]
>>> b = [2,5,6,11]
>>> for c in heapq.merge(a,b):
        print(c)

或许你对于这个例子更多的是不屑,因为你觉得用列表相加会更简单更方便,但是如果是一个列表一个元组呢?难道还要先遍历然后追加?那对于非常庞大的序列呢?从程序的优雅性以及高效性来说,heapq.merge() 还是值得拥有的

迭代器代替 while 无限循环

你在代码中使用 while 循环来迭代处理数据,因为它需要调用某个函数或者和一般迭代模式不同的测试条件。 能不能用迭代器来重写这个循环呢?
一个常见的 io 读取是这样的

CHUNKSIZE = 8192

def reader(s):
    while True:
        data = s.recv(CHUNKSIZE)
        if data == b'':
            break
        process_data(data)

当使用 iter 替代 while 后,你会不敢相信代码还能如此简短优雅

def reader2(s):
    for chunk in iter(lambda: s.recv(CHUNKSIZE), b''):
        process_data(data)
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值