实例:实现一个以深度优先方式遍历树形节点的生成器
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._children.append(node)
def __iter__(self):
return iter(self._children)
def depth_first(self):
yield self #返回节点
for c in self:#遍历子节点
yield from c.depth_first()
if __name__ == '__main__':
root = Node(0)
child1 = Node(1)
child2 = Node(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(Node(3))
child1.add_child(Node(4))
child2.add_child(Node(5))
for ch in root.depth_first():
print(ch)
yield之前已经讲过了,这里钟点介绍yield from原理
yield from原理:
yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器;返回迭代器。
举例,将
a= [1,2,3,4,4]
b =['a','b','c']
d ={'key':"people","value":"lian"}拼接成一个列表
方法一:
a= [1,2,3,4,4]
b =['a','b','c']
d ={'key':"people","value":"lian"}
def yieldtest(*args):
for item in args:
for i in item:
yield i
lis =yieldtest(a,b,d)
print(list(lis))
方法二:yield from
def yieldfrom(*args):
for item in args:
yield from item
a= [1,2,3,4,4]
b =['a','b','c']
d ={'key':"people","value":"lian"}
lis =yieldfrom(a,b,d)
print(list(lis))
综上可知,使用yield from更加简洁,另外需要声明的是,yield from它可以帮我们处理异常,我们不需要自己捕获异常并处理。
具体yield from
为我们做了哪些事,可以参考如下这段代码:
#一些说明
"""
_i:子生成器,同时也是一个迭代器
_y:子生成器生产的值
_r:yield from 表达式最终的值
_s:调用方通过send()发送的值
_e:异常对象
"""
_i = iter(EXPR)
try:
_y = next(_i)
except StopIteration as _e:
_r = _e.value
else:
while 1:
try:
_s = yield _y
except GeneratorExit as _e:
try:
_m = _i.close
except AttributeError:
pass
else:
_m()
raise _e
except BaseException as _e:
_x = sys.exc_info()
try:
_m = _i.throw
except AttributeError:
raise _e
else:
try:
_y = _m(*_x)
except StopIteration as _e:
_r = _e.value
break
else:
try:
if _s is None:
_y = next(_i)
else:
_y = _i.send(_s)
except StopIteration as _e:
_r = _e.value
break
RESULT = _r
重点:
认识yield from需要了解以下几个概念
1、调用方:调用委派生成器的客户端(调用方)代码
2、委托生成器:包含yield from表达式的生成器函数
3、子生成器:yield from后面加的生成器函数
举例:实现实时计算平均值
比如,第一次传入10,那返回平均数自然是10.
第二次传入20,那返回平均数是(10+20)/2=15
第三次传入30,那返回平均数(10+20+30)/3=20
# 子生成器
def average_gen():
count = 0
total = 0
average = 0
t = 0
while 1:
new_num = yield average
count += 1
total += new_num
average = total / count # 返回average
# 委托生成器
def proxy_gen():
while 1:
yield from average_gen()
# 调用方
def main():
gen = proxy_gen()
next(gen)
print(gen.send(10))
print(gen.send(20))
print(gen.send(30))
if __name__ == "__main__":
main()
委托生成器的作用是:在调用方与子生成器之间建立一个双向通道。
所谓的双向通道是什么意思呢?
调用方可以通过send()直接发送消息给子生成器,而子生成器yield的值,也是直接返回给调用方。
前面我们讲过使用reversed来实现链表逆序,如
l =[1,2,3,4]
for i in reversed(l):
print(i)
反向迭代仅仅当对象的大小可预先确定或者对象实现了 __reversed__()
的特殊方法时才能生效。 如果两者都不符合,那你必须先将对象转换为一个列表才行,数据量少还可以,但是一旦数据量上去了,必会占用大量内存,在这里给大家介绍一个鲜有人知的点,可以通过自定义__reversed__来节约内存,可以使得代码非常的高效, 因为它不再需要将数据填充到一个列表中然后再去反向迭代这个列表。
class Countdown:
def __init__(self, start):
self.start = start
# Forward iterator
def __iter__(self):
pass
# Reverse iterator
def __reversed__(self):
n = 1
while n <= self.start:
yield n
n += 1
for rr in reversed(Countdown(30)):
print(rr)
迭代器切片:函数 itertools.islice()
正好适用于在迭代器和生成器上做切片操作。比如:
# 子生成器
from itertools import islice
if __name__ == "__main__":
l = (i for i in range(1000))
# islice(iterable, stop) --> isliceobject
# islice(iterable, start, stop[, step]) --> isliceobject
for i in islice(l,1,5,2):
print(i)
跳过可迭代对象的开始部分:itertools.dropwhile()
函数。使用时,你给它传递一个函数对象和一个可迭代对象。 它会返回一个迭代器对象,丢弃原有序列中直到函数返回Flase之前的所有元素,然后返回后面所有元素。
from itertools import islice, dropwhile
if __name__ == "__main__":
# dropwhile(predicate, iterable) --> dropwhileobject
l = iter(('#hhe', "emm", "#ss", "map"))
for i in dropwhile(lambda i: i.startswith('#'), l):
print(i)
排列组合的迭代:itertools.permutations()
,它接受一个集合并产生一个元组序列, 通过打乱集合中元素排列顺序生成一个元组,第一个参数接受容器,第二个可选的长度参数, 比如:
from itertools import islice, dropwhile,permutations
if __name__ == "__main__":
l = [1,2,3]
for i in permutations(l,2):
print(i)
(1, 2)
(1, 3)
(2, 1)
(2, 3)
(3, 1)
(3, 2)
itertools.combinations()和itertools.permutations效果差不多,但是
itertools.combinations不考虑顺序,在计算组合的时候,一旦元素被选取就会从候选中剔除掉,而 itertools.combinations_with_replacement()
允许同一个元素被选择多次
# 子生成器
from itertools import islice, dropwhile,permutations,combinations,combinations_with_replacement
if __name__ == "__main__":
l = [1,2,3]
for i in combinations(l,3):
print(i)
for i in combinations(l,2):
print(i)
print("-------")
for i in combinations_with_replacement(l,3):
print(i)
for i in combinations_with_replacement(l,2):
print(i)
序列上索引值迭代:使用内置的 enumerate()
函数
if __name__ == "__main__":
l = ['one','two','three']
for i,j in enumerate(l,2):
print(i,j)
第二个参数表示从行号2开始算起,默认1
同时迭代多个序列:zip(a, b)
会生成一个可返回元组 (x, y)
的迭代器,其中x来自a,y来自b。 一旦其中某个序列到底结尾,迭代宣告结束。 因此迭代长度跟参数中最短序列长度一致。如果这个不是你想要的效果,那么还可以使用 itertools.zip_longest()
函数来代替
if __name__ == "__main__":
l = ['one','two','three']
ll = [1,2,3]
lll=[4,6,7]
for i,j,k in zip(l,ll,lll):
print(i,j,k)
output:one 1 4
two 2 6
three 3 7
from itertools import zip_longest
if __name__ == "__main__":
l = ['one','two','three']
ll = [1,2,3]
lll=[4,6,7,9]
for i,j,k in zip_longest(l,ll,lll):
print(i,j,k)
output:
one 1 4
two 2 6
three 3 7
None None 9
不同集合上元素的迭代:在多个对象执行相同的操作,但是这些对象在不同的容器中,你希望代码在不失可读性的情况下避免写重复的循环,itertools.chain()
方法可以用来简化这个任务。 它接受一个可迭代对象列表作为输入,并返回一个迭代器,有效的屏蔽掉在多个容器中迭代细节。
from itertools import zip_longest,chain
if __name__ == "__main__":
l = ['one','two','three']
ll = [1,2,3]
lll=[4,6,7,9]
for i in chain(l,ll):
print(i)
顺序迭代合并后的排序迭代对象:你有一系列排序序列,想将它们合并后得到一个排序序列并在上面迭代遍历
import heapq
if __name__ == "__main__":
ll = [1,2,9,10,3]
lll=[4,6,7]
for i in heapq.merge(ll,lll):
print(i)
iter
函数一个鲜为人知的特性是它接受一个可选的 callable
对象和一个标记(结尾)值作为输入参数。 当以这种方式使用的时候,它会创建一个迭代器, 这个迭代器会不断调用 callable
对象直到返回值和标记值相等为止。
这种特殊的方法对于一些特定的会被重复调用的函数很有效果,比如涉及到I/O调用的函数。 举例来讲,如果你想从套接字或文件中以数据块的方式读取数据,通常你得要不断重复的执行 read()
或 recv()
, 并在后面紧跟一个文件结尾测试来决定是否终止。这节中的方案使用一个简单的 iter()
调用就可以将两者结合起来了。 其中 lambda
函数参数是为了创建一个无参的 callable
对象,并为 recv
或 read()
方法提供了 size
参数。
import sys
f = open('/etc/passwd')
for chunk in iter(lambda: f.read(10), ''):
n = sys.stdout.write(chunk)