迭代器的底层机制
下面一段代码向我们演示了迭代期间发生的基本细节
items = [1, 2, 3]
it = iter(items)
print(next(it))
# 输出1
print(next(it))
# 输出2
print(next(it))
# 输出3
print(next(it))
# 报了一个异样如下
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
从上面的列子可以看出,对于一个迭代器,当调用next()函数的时候,会将序列里面的元素一个一个返回,当序列里面的元素已经迭代完成的时候,再去调用next()就会抛出一个StopIteration的异常。
所以我们可以不使用for循环,通过以下的方式来手动遍历一个迭代器
with open("demo.txt") as fp:
try:
while True:
line = next(fp)
print(line, end="")
except StopIteration:
pass
通常来讲,StopIteration用来指示迭代的结尾。你还可以通过返回一个制定值来标记结尾,比如None。
with open("demo.txt") as fp:
while True:
line = next(fp, None)
if line is None:
break
print(line, end="")
生成器
下面是一个生成器函数,用来生产某个范围内的数字。
def frange(start, stop, increment):
x = start
while x < stop:
yield x
x += increment
你可以使用for循环来使用生面这个函数
for n in frange(0, 4, 0.5):
print(n)
# 输出
# 0
# 0.5
# 1
# 1.5
# ...
list(frange(0, 3, 1))
# [0, 1, 2]
一个函数中如果有yield语句,那么这个函数就是一个生成器函数。和普通函数不同的是,生成器只能用于迭代操作。
下面这个函数向你展示了生成器函数的底层工作机制
def countdown(n):
print("Starting to count from", n)
while n > 0:
yield n
n -= 1
print("Done!")
c = countdown(3)
print(next(c))
# 输出
# Starting to count from 3
# 3
print(next(c))
# 2
print(next(c))
# 1
print(next(c))
# Done!
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration
自己对于yield关键字的简单理解
我们可以将yield简单的裂解成一个return,但是它和return有一定的区别。例如上面的那个countdown的列子,当我们调用一次next()
countdown()函数会执行,直到遇见yield这个关键字,yield会将器后面的n返回,并停在这里。当第二次调用next的时候函数会继续执行,直到下一次遇见yield。依次类推,直到所有的yield都执行完,最后抛出一个异常。
更多更加详细的关于yield的理解大家可以参考下面的文章
https://www.jianshu.com/p/9dd355ab4e5d
实现一个自定义可迭代对象
要自定义一个可迭代对象,要求类实现__iter__()
魔法方法返回一个特殊的迭代器对象,这个迭代器对象实现了一个__next__()
方法,并通过StopIteration异常标识迭代的完成。但是这些东西会非常繁琐,通常我们可以用下面的简单方法实现。
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._chirldren.append(node)
def __iter__(self):
return iter(self._children)
# 下面这个函数是通过深度优先的方式遍历这个树,可以不用理解
def depth_first(self):
yield self
for c in self:
yield from c.depth_first()
像上面的类我们就实现了一个简单的自定义可迭代的类了
反向迭代
a = [1, 2, 3, 4]
for x in reversed(a):
print(x)
4
3
2
1
反向迭代,仅仅当对象的大小可预先确定或者对象实现来了__reversed__()
方法是才能生效。
我们可以在自定义类上实现__reversed__()
方法来实现反向迭代
# -*-coding:utf-8-*-
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 Countdown(30):
print(r, end=" ")
print()
for r in reversed(Countdown(30)):
print(r, end=" ")
迭代器切片
函数itertools.islice()正好使用于在迭代器和生成器上做切片操作。
def count(n):
while True:
yield n
n += 1
c = count(0)
c[10:20] # 使用该方法获取切片会报下面的错误
"""
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'generator' object is not subscriptable
"""
# 可以像下面这样
import itertools
for x in itertools.islice(c, 10, 20)
print(x)
10
11
12
13
14
15
16
17
18
19
迭代器和生成器我们不能使用标准的切片曹祖,因为他们的长度事先我们并不知道(并且也没有事先索引)。函数islice()返回一个可以生成制定元素的迭代器,它通过遍历并丢弃直到切片开始索引位置的所有元素。然后才开始一个一个的返回元素,并直到切片结束索引位置。
这里需要重点强调一点的是islice会消耗传入的迭代器中的数据。必须考虑到迭代器是不可逆的这个事实。如果你需要之后再次访问这个迭代器的话,就得将它里面得数据放入一个列表中。
展开嵌套的序列
你想将一个多层嵌套的序列展开成一个单层序列。可以写一个包含yield from语句的递归轻松解决这个问题。
from collection 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)
itertools模块一些常用的函数
- chain()
- dropwhile()
- islice()
- permutation()
- combination()
- enumerate()
- zip_longest()