迭代器与生成器

迭代是Python最强大的功能之一,虽然看起来迭代只是处理序列中元素的一种方法,但不仅仅如此。

手动遍历迭代器

想遍历但不想使用for循环。

使用next()方法并在代码中捕获StopIteration异常。

StopIteration用来指示迭代的结尾,也可以通过返回指定结尾。

  1. l = next(iterator, None)

代理迭代

构建了一个自定义容器对象,想在这个容器上执行迭代操作。

只需定义__iter__()方法,将迭代操作代理到容器内部对象上。

  1. class Node:
  2. def __init__(self, value):
  3. self._value = value
  4. self._children = []
  5. def __repr__(self):
  6. return 'Node({!r})'.format(self._value)
  7. def add_child(self, node):
  8. self._children.append(node)
  9. def __iter__(self):
  10. return iter(self._children)
  11. # Example
  12. if __name__ == '__main__':
  13. root = Node(0)
  14. child1 = Node(1)
  15. child2 = Node(2)
  16. root.add_child(child1)
  17. root.add_child(child2)
  18. # Outputs Node(1), Node(2)
  19. for ch in root:
  20. print(ch)

迭代器协议需要__iter__()方法返回一个实现了__next__()方法的迭代器对象。

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

只需要实现yield语句即可转换为生成器,并且生成器只能用于迭代操作。

实现迭代器协议

最简单的是使用生成器函数,否则需要实现__iter__()__next__()方法并完成对StopIteration异常的捕捉。

  1. class Node:
  2. def __init__(self, value):
  3. self._value = value
  4. self._children = []
  5. def __repr__(self):
  6. return 'Node({!r})'.format(self._value)
  7. def add_child(self, node):
  8. self._children.append(node)
  9. def __iter__(self):
  10. return iter(self._children)
  11. def depth_first(self):
  12. yield self
  13. for c in self:
  14. yield from c.depth_first()
  15. # Example
  16. if __name__ == '__main__':
  17. root = Node(0)
  18. child1 = Node(1)
  19. child2 = Node(2)
  20. root.add_child(child1)
  21. root.add_child(child2)
  22. child1.add_child(Node(3))
  23. child1.add_child(Node(4))
  24. child2.add_child(Node(5))
  25. for ch in root.depth_first():
  26. print(ch)
  27. # Outputs Node(0), Node(1), Node(3), Node(4), Node(2), Node(5)
  1. class Node2:
  2. def __init__(self, value):
  3. self._value = value
  4. self._children = []
  5. def __repr__(self):
  6. return 'Node({!r})'.format(self._value)
  7. def add_child(self, node):
  8. self._children.append(node)
  9. def __iter__(self):
  10. return iter(self._children)
  11. def depth_first(self):
  12. return DepthFirstIterator(self)
  13. class DepthFirstIterator(object):
  14. '''
  15. Depth-first traversal
  16. '''
  17. def __init__(self, start_node):
  18. self._node = start_node
  19. self._children_iter = None
  20. self._child_iter = None
  21. def __iter__(self):
  22. return self
  23. def __next__(self):
  24. # Return myself if just started; create an iterator for children
  25. if self._children_iter is None:
  26. self._children_iter = iter(self._node)
  27. return self._node
  28. # If processing a child, return its next item
  29. elif self._child_iter:
  30. try:
  31. nextchild = next(self._child_iter)
  32. return nextchild
  33. except StopIteration:
  34. self._child_iter = None
  35. return next(self)
  36. # Advance to the next child and start its iteration
  37. else:
  38. self._child_iter = next(self._children_iter).depth_first()
  39. return next(self)

通常没人会去写第二种复杂的代码,又要维护状态又要处理异常,因此最好是使用生成器来实现。

反向迭代

使用reversed()方法,并且要注意,反向迭代必须是对象大小确定或该对象实现了__reversed__()方法才能生效,两者都不符合就需要将对象转换为列表才行。

  1. # Print a file backwards
  2. f = open('somefile')
  3. for line in reversed(list(f)):
  4. print(line, end='')

还要注意的是,如果可迭代对象元素很多,转换为列表会消耗大量内存。

  1. class Countdown:
  2. def __init__(self, start):
  3. self.start = start
  4. # Forward iterator
  5. def __iter__(self):
  6. n = self.start
  7. while n > 0:
  8. yield n
  9. n -= 1
  10. # Reverse iterator
  11. def __reversed__(self):
  12. n = 1
  13. while n <= self.start:
  14. yield n
  15. n += 1
  16. for rr in reversed(Countdown(30)):
  17. print(rr)
  18. for rr in Countdown(30):
  19. print(rr)

带有外部状态的生成器函数

如果想定义一个生成器函数,同时调用某个想暴露给用户使用的状态值,最简单的方法是实现一个类,然后把生成器函数放到__iter__()方法中。

  1. from collections import deque
  2. class linehistory:
  3. def __init__(self, lines, histlen=3):
  4. self.lines = lines
  5. self.history = deque(maxlen=histlen)
  6. def __iter__(self):
  7. for lineno, line in enumerate(self.lines, 1):
  8. self.history.append((lineno, line))
  9. yield line
  10. def clear(self):
  11. self.history.clear()

迭代器切片

想得到一个由迭代器/生成器生成的切片对象,使用itertools模块的islice()方法。

  1. >>> def count(n):
  2. ... while True:
  3. ... yield n
  4. ... n += 1
  5. ...
  6. >>> c = count(0)
  7. >>> c[10:20]
  8. Traceback (most recent call last):
  9. File "<stdin>", line 1, in <module>
  10. TypeError: 'generator' object is not subscriptable
  11. >>> # Now using islice()
  12. >>> import itertools
  13. >>> for x in itertools.islice(c, 10, 20):
  14. ... print(x)
  15. ...
  16. 10
  17. 11
  18. 12
  19. 13
  20. 14
  21. 15
  22. 16
  23. 17
  24. 18
  25. 19
  26. >>>

跳过可迭代对象的开始部分

itertools模块的dropwhile()函数,传入一个函数对象和一个可迭代对象,返回一个可迭代对象,类似filter()方法,丢弃函数返回True的元素。

  1. >>> from itertools import dropwhile
  2. >>> with open('/etc/passwd') as f:
  3. ... for line in dropwhile(lambda line: line.startswith('#'), f):
  4. ... print(line, end='')

如果知道元素个数,也可以用islice()方法来抛弃前n个元素。

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

None的作用与切片的[3:]原理相同。

排列组合的迭代

有时需要遍历一个集合中元素的所有可能的排列或组合。

itertools模块提供了三个函数来解决此类问题。

permutaions()接受可迭代对象和可选的长度参数,生成基于指定长度的排列元组。

  1. >>> items = ['a', 'b', 'c']
  2. >>> from itertools import permutations
  3. >>> for p in permutations(items):
  4. ... print(p)
  5. ...
  6. ('a', 'b', 'c')
  7. ('a', 'c', 'b')
  8. ('b', 'a', 'c')
  9. ('b', 'c', 'a')
  10. ('c', 'a', 'b')
  11. ('c', 'b', 'a')
  12. >>> for p in permutations(items, 2):
  13. ... print(p)
  14. ...
  15. ('a', 'b')
  16. ('a', 'c')
  17. ('b', 'a')
  18. ('b', 'c')
  19. ('c', 'a')
  20. ('c', 'b')
  21. >>>

combinations()接收可迭代对象和必选的长度参数,返回组合元组。

  1. >>> from itertools import combinations
  2. >>> for c in combinations(items, 3):
  3. ... print(c)
  4. ...
  5. ('a', 'b', 'c')
  6. >>> for c in combinations(items, 2):
  7. ... print(c)
  8. ...
  9. ('a', 'b')
  10. ('a', 'c')
  11. ('b', 'c')
  12. >>> for c in combinations(items, 1):
  13. ... print(c)
  14. ...
  15. ('a',)
  16. ('b',)
  17. ('c',)
  18. >>>

若要允许同一元素被多次选择,可使用combinations_with_replacement()方法。

枚举迭代

在迭代的同时跟踪被处理的元素的下标索引。

使用内置的enumerate()方法,接收一个可迭代对象和可选的初始值,返回一个迭代器。

同时迭代多个序列

使用zip()方法,该方法需要注意以最短序列长度为迭代基准,超过长度不迭代。

或可使用itertools.zip_longest()方法,接收多个可迭代对象和一个fillvalue关键字参数指定默认值,此方法会迭代到最长序列。

注意的是zip()方法返回一个迭代器而不是列表。

同时对多个可迭代对象进行迭代

避免写重复的循环,使用itertools模块的chain()方法组合多个可迭代对象并返回一个新的迭代器。

  1. >>> from itertools import chain
  2. >>> a = [1, 2, 3, 4]
  3. >>> b = ['x', 'y', 'z']
  4. >>> for x in chain(a, b):
  5. ... print(x)
  6. ...
  7. 1
  8. 2
  9. 3
  10. 4
  11. x
  12. y
  13. z
  14. >>>

数据处理管道

使用生成器函数来实现管道机制。读取文件做一个生成器,读取行做一个生成器,处理行做一个生成器,最后用一个循环或相应方法调用,形成一个数据管道,注意的是yieldyield from的区别。

展开嵌套的序列

利用yield from后接可迭代对象会返回其所有元素的特点来调用,避免重复的循环代码,更优雅。

注意要判断是否是可迭代对象。yield from对于在生成器中调用其他生成器很有用。

合并有序序列并生成有序可迭代对象

有时需要将多个有序序列合并成一个有序序列,使用heapq.merge()方法可以解决。

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

由于可迭代特性,heapq.merge()不会立刻读取所有序列,因此在长序列中使用不会有太大开销,并且必须注意的是,输入的序列必须是排序过的,heapq.merge()方法不会检查顺序,这个方法只是比较多个序列中的首位值,较小的放入新的序列。

迭代器代替while无限循环

利用了iter()方法的一个特性来做无限循环或有条件的循环。该方法接收一个可调用对象,iter()方法不断调用该对象直到其返回值与标记值相等为止。

  1. >>> bool(iter(int, 1))
  2. True

int默认值是0,因此iter迭代器永远不会结束,所以其布尔值始终是True。

  1. CHUNKSIZE = 8192
  2. def reader(s):
  3. while True:
  4. data = s.recv(CHUNKSIZE)
  5. if data == b'':
  6. break
  7. process_data(data)
  8. def reader2(s):
  9. for chunk in iter(lambda: s.recv(CHUNKSIZE), b''):
  10. pass
  11. # process_data(data)

PythonCookBook笔记——迭代器与生成器的更多相关文章

  1. Python学习笔记——迭代器和生成器

    1.手动遍历迭代器 使用next函数,并捕获StopIteration异常. def manual_iter(): with open('./test.py') as f: try: while Tr ...

  2. Python 从零学起(纯基础) 笔记 之 迭代器、生成器和修饰器

    Python的迭代器. 生成器和修饰器 1. 迭代器是访问集合元素的一种方式,从第一个到最后,只许前进不许后退. 优点:不要求事先准备好整个迭代过程中的所有元素,仅仅在迭代到某个元素时才计算该元素,而 ...

  3. python学习笔记四 迭代器,生成器,装饰器(基础篇)

    迭代器 __iter__方法返回一个迭代器,它是具有__next__方法的对象.在调用__next__方法时,迭代器会返回它的下一个值,若__next__方法调用迭代器 没有值返回,就会引发一个Sto ...

  4. Python复习笔记(八)迭代器和生成器和协程

    1. 迭代器 1.1 可迭代对象 判断xxx_obj是否可以迭代 在第1步成立的前提下,调用 iter 函数得到 xxx_obj 对象的 __iter__ 方法的返回值 __iter__ 方法的返回值 ...

  5. Python学习笔记(4):容器、迭代对象、迭代器、生成器、生成器表达式

    在了解Python的数据结构时,容器(container).可迭代对象(iterable).迭代器(iterator).生成器(generator).列表/集合/字典推导式(list,set,dict ...

  6. 流畅python学习笔记:第十四章:迭代器和生成器

    迭代器和生成器是python中的重要特性,本章作者花了很大的篇幅来介绍迭代器和生成器的用法. 首先来看一个单词序列的例子: import re re_word=re.compile(r'\w+') c ...

  7. Python学习笔记:输入输出,注释,运算符,变量,数字类型,序列,条件和循环控制,函数,迭代器与生成器,异常处理

    输入输出 输入函数input()和raw_input() 在Python3.x中只有input()作为输入函数,会将输入内容自动转换str类型: 在Python2.x中有input()和raw_inp ...

  8. Python笔记(十)_迭代器与生成器

    迭代 用for...in来遍历一个可迭代对象的过程就叫迭代 可迭代对象:列表.元组.字典.集合.字符串.生成器 可以使用内置函数isinstance()判断一个对象是否是可迭代对象 >>& ...

  9. Python3 迭代器与生成器 - 学习笔记

    可迭代对象(Iterable) 迭代器(Iterator) 定义 迭代器和可迭代对象的区别 创建一个迭代器 创建一个迭代器类 使用内置iter()函数 StopIteration异常 生成器(gene ...

随机推荐

  1. Java面试题之Java中==和equals()和hashCode()的区别

    “==”: ==是运算符,用来比较两个值.两个对象的内存地址是否相等: “equals()”: equals是Object类的方法,默认情况下比较两个对象是否是同一个对象,内部实现是通过“==”来实现 ...

  2. bzoj 3625小朋友和二叉树 多项式求逆+多项式开根 好题

    题目大意 给定n种权值 给定m \(F_i表示权值和为i的二叉树个数\) 求\(F_1,F_2...F_m\) 分析 安利博客 \(F_d=F_L*F_R*C_{mid},L+mid+R=d\) \( ...

  3. bzoj 1185 [HNOI2007]最小矩形覆盖 凸包+旋转卡壳

    题目大意 用最小矩形覆盖平面上所有的点 分析 有一结论:最小矩形中有一条边在凸包的边上,不然可以旋转一个角度让面积变小 简略证明 我们逆时针枚举一条边 用旋转卡壳维护此时最左,最右,最上的点 注意 注 ...

  4. 从网上搜集的X86 显示 int 10H

    INT 10H 是由 BIOS 对屏幕及显示器所提供的服务程序,而后倚天公司针对倚天中文提供了许多服务程序,这些服务程序也加挂在 INT 10H 内.使用 INT 10H 中断服务程序时,先指定 AH ...

  5. Larevel5.1 打印SQL语句

    Larevel5.1 打印SQL语句 为了方便调试,开发时需要打印sql. 方法一(全局打开): SQL打印默认是关闭的, 需要在/vendor/illuminate/database/Connect ...

  6. 洛谷—— P1134 阶乘问题

    https://www.luogu.org/problemnew/show/P1134 题目描述 也许你早就知道阶乘的含义,N阶乘是由1到N相乘而产生,如: 12! = 1 x 2 x 3 x 4 x ...

  7. Oracle中PL/SQL 范例

    1.写匿名块,输入三角形三个表的长度.在控制台打印三角形的面积 declare v_side_first ):=&第一条边; v_side_second ):=&第二条边; v_sid ...

  8. go语言:函数参数传递详解

    参数传递是指在程序的传递过程中,实际参数就会将参数值传递给相应的形式参数,然后在函数中实现对数据处理和返回的过程.比较常见的参数传递有:值传递,按地址传递参数或者按数组传递参数. 1.常规传递 使用普 ...

  9. OpenLayers3 动画

    参考文章 openlayers3中三种动画实现

  10. 【postman】postman测试API报错如下:TypeError: Failed to execute 'fetch' on 'Window': Invalid value 对中文支持不好

    使用postman测试APi的时候,因为系统需要在header部带上登录用户的信息,所以 如下: 然后测试报错如下:TypeError: Failed to execute 'fetch' on 'W ...