python迭代器和生成器

迭代器的底层机制

下面一段代码向我们演示了迭代期间发生的基本细节

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()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值