迭代器与生成器

迭代器

  • 什么是迭代器?迭代器对象就是对象支持迭代器协议换而言之就是:实现对象的__iter__() 和 __next__() 方法。
  1. 其中__iter__()返回对象本身
  2. __next__()返回容器中的下一个元素
  • 可迭代对象与迭代器对象有什么区别
  • 可迭代对象是指所有可以迭代的对象
  • 迭代器对象就是通过惰性机制通过next()方法调用元素的对象。

自定义迭代器对象

class MyRange(object):
	def __init__(self, n):
		self.idx = 0
		self.n = n
		
	def __iter__(self):
		return self
		
	def __next__(self):
		if self.idx < self.n:
			val = self.idx
			self.idx += 1
			return val
		else:
			raise StopIteration()

输出结果

>>> for i in MyRange(3):
...     print(i)
...
0
1
2

解决迭代器对象没有办法多次迭代的问题

class Frange(object):
	def __init__(self, n):
		self.n = n
		
	def __iter__(self):
		return SubRange(self.n)
		
class SubRange(object):
	def __init__(self, n):
		self.n = n
		self.id = 0
		
	def __iter__(self):
		return self
		
	def __next__(self):
		if self.id < self.n:
			val = self.id
			self.id += 1
			return val
		else:
			raise StopIteration()

输出结果如下

>>> for i in Frange(3):
...     print(i)
...
0
1
2
>>> for i in Frange(3):
...     print(i)
...
0
1
2

生成器

  • 在Python中,使用生成器可以很方便的支持迭代器协议。生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果,在每个结果之间挂起和继续它们的状态,来自动实现迭代协议。
  • 也就是说,yield是一个语法糖,内部实现支持了迭代器协议,同时yield内部是一个状态机,维护着挂起和继续的状态。

弄明白生成器动作原理

def zrange(n):
	print("生成器函数开始工作")
	i = 0
	while i < n:
		print("yield 执行之前", i)
		yield i
		i += 1
		print("yield 执行之后",i)
	print("生成器函数执行完毕")

输出如下

>>> zranged = zrange(3)
>>> for i in zranged:
...     print(i)
...
生成器函数开始工作
yield 执行之前 0
0
yield 执行之后 1
yield 执行之前 1
1
yield 执行之后 2
yield 执行之前 2
2
yield 执行之后 3
生成器函数执行完毕

生成器表达式

>>> make_reg = (x for x in range(10))# 此处就是生成器表达式,语法规则和列表推导式特别相似
>>> for i in make_reg:
...     print(i)
...
0
1
2
3
4
5
6
7
8
9
>>> for i in make_reg:
...     print(i)
...

递归生成器

def permutations(li):
    if len(li) == 0:
        yield li [012] 
    else:
        for i in range(len(li)):
            li[0], li[i] = li[i], li[0]
            for item in permutations(li[1:]):
                yield [li[0]] + item
    
for item in permutations(range(3)):
    print(item)

生成器的send()和close()

  • 调用send()方法相当于给yield传递了一个参数,同时执行了__next__()方法
  • 调用close()用于关闭生成器当执行此方法之后再调用send() 或 __next__()会有异常

使用send 需注意地方,在第一次调用生成器的时候需要先调用__next__()或者__send__(None),因为在第一次调用时假如send传递非None参数此时没有yield参数来接收参数,也就是说send传递非空参数时生成器必须处于挂起状态。

def zrange(n):
	i = 0
	while i < n:
		val = yield i
		print("val is ",val)
		i += 1
>>> zr = zrange(5)
>>> print(zr.send())# 第一次调用send()此时会报错
##############结果报错如下
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: send() takes exactly one argument (0 given)
#############可见send必须传入一个参数而且不能是空的


>>> print(zr.send(8))#此时传入一个非空参数
############执行结果
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't send non-None value to a just-started generator
############


>>> print(zr.__next__())
0
>>> print(zr.send(9))
val is  9
1

生成器迭代器总结

  1. 通过实现迭代器协议对应的__iter__()和next()方法,可以自定义迭代器类型。对于可迭代对象,for语句可以通过iter()方法获取迭代器,并且通过next()方法获得容器的下一个元素
  2. 像列表这种序列类型的对象,可迭代对象和迭代器对象是相互独立存在的,在迭代的过程中各个迭代器相互独立;但是,有的可迭代对象本身又是迭代器对象,那么迭代器就没法独立使用。
  3. itertools模块提供了一系列迭代器,能够帮助用户轻松地使用排列、组合、笛卡尔积或其他组合结构
  4. 生成器是一种特殊的迭代器,内部支持了生成器协议,不需要明确定义__iter__()和next()方法
  5. 生成器通过生成器函数产生,生成器函数可以通过常规的def语句来定义,但是不用return返回,而是用yield一次返回一个结果
上一篇:课程学习记录之python迭代器和生成器


下一篇:【ACWing】44. 分行从上往下打印二叉树