简单的说,一个对象(在Python里面一切都是对象)只要实现了__iter__()方法,那么用isinstance()函数1检查就是Iterable对象。
from collections.abc import Iterable
print(isinstance([], Iterable)) # True list 是可迭代的
print(isinstance({}, Iterable)) # True 字典是可迭代的
print(isinstance((), Iterable)) # True 元组是可迭代的
print(isinstance(set(), Iterable)) # True set是可迭代的
print(isinstance('', Iterable)) # True 字符串是可迭代的
# 或者可以通过dir([])获取对象内的方法是否包含__iter__()
currPath = os.path.dirname(os.path.abspath(__file__))
with open(currPath+'/model.py') as file:
print(isinstance(file, Iterable)) # True
在类中定义了__iter__()方法的对象,可以被认为是 Iterable对象,但自定义的可迭代对象要能在for循环中正确使用,就需要保证__iter__()实现必须是正确的。
class IterObj:
def __iter__(self):
return self
it = IterObj()
print(iter(it))
以上的IterObj转为迭代器时出现了类型错误,意思是iter()函数不能将‘非迭代器’类型转成迭代器。
那如何才能将一个可迭代(Iterable)对象转成迭代器(Iterator)对象呢?
class IterObj:
def __init__(self):
self.a = []
def __iter__(self):
return iter(self.a)
在定义一个可迭代对象时,我们要非常注意__iter__()方法的内部实现逻辑,一般情况下,是通过一些已知的可迭代对象(例如,上文提到的集合、序列、文件等或其他正确定义的可迭代对象)来辅助我们来实现。
在类中实现了如果只实现__getitem__()的对象可以通过iter()函数转化成迭代器但其本身不是可迭代对象。所以当一个对象能够在for循环中运行,但不一定是Iterable对象。
以下实例说明了可以在for中使用的对象,不一定是可迭代对象:
from collections.abc import Iterable, Iterator, Generator # 容器的抽象基类
class IterObj:
def __init__(self):
self.a = [1, 2, 3, 4, 5]
def __getitem__(self, index):
return self.a[index]
it = IterObj()
print(isinstance(it, Iterable)) # False
print(isinstance(it, Iterator)) # False
print(isinstance(it, Generator)) # False
print(hasattr(it, "__iter__")) # False
print(iter(it)) #
for i in it:
print(i) # 将打印出1、2、3、4、5
简单来说,一个对象实现了__iter__()和__next__()方法,那么它就是一个迭代器对象。 例如:
这是一个简单的生成2的幂的迭代器,我们在__iter__里为self.n初始化为0,然后返回自身。在__next__里判断有没有迭代结束,如果结束的话抛出一个异常。
class PowTwo:
"""Class to implement an iterator
of powers of two"""
def __init__(self, max_len=0):
self.max_len = max_len
def __iter__(self):
self.n = 0
return self
def __next__(self):
if self.n <= self.max_len:
result = 2 ** self.n
self.n += 1
return result
else:
raise StopIteration
it = PowTwo(2)
print(isinstance(it, Iterable)) # True
print(isinstance(it, Iterator)) # True
print(isinstance(it, Generator)) # False
print(hasattr(it, "__iter__")) # True
print(hasattr(it, "__next__")) # True
i = iter(it)
print(next(i)) # 1
print(next(i)) # 2
print(next(i)) # 4
我们也可以用for循环来迭代它:
for i in PowTwo(5):
print(i) # 1 2 4 8 16 32
需要注意的是: 集合和序列等对象是可迭代的但不是迭代器,文件对象是迭代器。
from collections.abc import Iterable, Iterator, Generator # 容器的抽象基类
print(isinstance([], Iterator)) # False
print(isinstance({}, Iterator)) # False
print(isinstance((), Iterator)) # False
print(isinstance(set(), Iterator)) # False
print(isinstance('', Iterator)) # False
curr_path = os.path.dirname(os.path.abspath(__file__))
with open(curr_path + '/model.py') as file:
print(isinstance(file, Iterator)) # True
一个迭代器(Iterator)对象不仅可以在for循环中使用,还可以通过内置函数next()函数进行调用。例如:
it = IterObj()
print(next(it)) # 1
print(next(it)) # 2
迭代器除了可以迭代一个容器或者是像上面这样自定义迭代方法之外,还可以用来迭代生成器。下面就让我们一起来看下生成器的概念。
简单来说,生成器既是可迭代的也是迭代器。
from collections.abc import Iterable, Iterator, Generator # 容器的抽象基类
nums = (x for x in range(100)) # 0~99的生成器
print(isinstance(nums, Iterable)) # True
print(isinstance(nums, Iterator)) # True
print(isinstance(nums, Generator)) # True
print(hasattr(nums, "__iter__")) # True
print(hasattr(nums, "__next__")) # True
print(next(nums)) # 0
print(next(nums)) # 1
列表生成器可以不需要消耗大量的内存来生成一个巨大的列表,只有在需要数据的时候才会进行计算。
from collections.abc import Iterable, Iterator, Generator # 容器的抽象基类
def gen():
for i in range(10):
yield i
auto_gen = gen()
print(isinstance(auto_gen, Iterable)) # True
print(isinstance(auto_gen, Iterator)) # True
print(isinstance(auto_gen, Generator)) # True
print(hasattr(auto_gen, "__iter__")) # True
print(hasattr(auto_gen, "__next__")) # True
print(next(auto_gen)) # 0
print(next(auto_gen)) # 1
接下来要介绍的yield from和yield用法差不多,也是从生成器返回一个结果,并且下次执行的时候从返回的位置开始继续执行。
但是它有一点和yield不同,我们来看一个经典的例子。
def g1():
yield range(5)
it1 = next(g1())
for x in it1:
print(x)
def g2():
yield from range(5)
it2 = g2()
for x in it2:
print(x)
这两者打印出来的结果是一样的,但是逻辑完全不同。在第一个生成器g1当中,直接通过yield返回了一个迭代器。也就是说我们for循环执行的其实是range(5),而第二个生成器g2则通过yield from获取了range(5)这个迭代器当中的值进行的返回。
也就是说yield from可以返回一个迭代器或者是生成器执行next之后的结果。
最后,我们来看一个yield from使用的一个经典场景:二叉树的遍历:
class Node:
def __init__(self, key):
self.key = key
self.l_child = None
self.r_child = None
self.iterated = False
self.father = None
def iterate(self):
if self.l_child is not None:
yield from self.l_child.iterate()
yield self.key
if self.r_child is not None:
yield from self.r_child.iterate()
在这个代码当中我们定义了二叉树当中的一个节点,以及它对应的迭代方法。由于我们用到了yield来返回结果,所以iterate方法本质是一个生成器。再来看iterate方法内部,我们通过yield from调用了iterate,所以我们在执行的时候,它会自动继续解析node.lchild的iterate,也就是说我们通过yield from实现了递归。
当我们建好树之后,可以直接使用root.iterate来遍历整棵树。
class Tree:
def __init__(self):
#建树过程
self.root = Node(4)
self.root.lchild = Node(3)
self.root.lchild.father = self.root
self.root.rchild = Node(5)
self.root.rchild.father = self.root
self.root.lchild.lchild = Node(1)
self.root.lchild.lchild.father = self.root.lchild
self.root.rchild.rchild = Node(7)
self.root.rchild.rchild.father = self.root.rchild
def iterate(self):
yield from self.root.iterate()
tree = Tree()
for i in tree.iterate():
print(i)
通过yield from,我们可以很轻松地利用递归的思路来实现树上的生成器。从而可以很方便地以生成器的思路来遍历树上所有的元素。
其实可以这么理解,迭代器和生成器遍历元素的方式是一样的,都是通过调用next来获取下一个元素。我们通过yield创建函数,返回的结果其实就是生成器生成的数据的迭代器。也就是说迭代器只是迭代和获取数据的,但是并不能无中生有地创造数据。而生成器的主要作用是创造数据,它生成出来的数据是以迭代器的形式返回的。
用协程实现的生产者-消费者模型的功能,实现了CPU在两个函数之间进行切换从而实现并发的效果。
def producer(c):
n = 0
while n < 5:
n += 1
print('producer {}'.format(n))
r = c.send(n)
print('consumer return {}'.format(r))
def consumer():
r = ''
while True:
n = yield r
if not n:
return
print('consumer {} '.format(n))
r = 'ok'
if __name__ == '__main__':
c = consumer()
next(c) # 启动consumer
producer(c)
参考文献:
isinstance()是Python中的一个内建函数,用来判断一个对象的变量类型。 ↩︎