在一种语言中有些语法元素属于高级语法元素,有着一些有趣或者难以理解的特性,其中最常见的是迭代器、生成器、装饰器、上下文管理器本文将介绍迭代器、生成器、上下文管理器。
内部持有__iter__()方法的对象。
迭代是Python最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。任何实现了__iter__和__next__()方法即迭代器协议的对象都是迭代器。__iter__返回迭代器自身【对应iter()方法】,__next__返回容器中的下一个值【next(generator),只能依次正序生成,生成数据不能再次生成。所有生成器不能超过迭代次数
】。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。字符串,列表或元组对象都可用于创建迭代器。
通过上面我们可以知道next()可以返回迭代器的下一个值,但有时候我们需要它自动访问。我们可以用for循环。
with open("exhibitionism.txt") as f:
try:
while True:
line=next(f)
print(line,end=' ')
except StopIteration:
pass
#----等价于
with open("exhibitionism.txt") as f:
while True:
line=next(f,None)
if line is None:
break
print(line,end='')
注:使用next函数可以了解底层迭代器精细控制的情况。
for循环内部三件事:
让自己的新容器对象【内部包含持有一个可迭代对象】能够完成迭代操作。一般来说我们要定义一个__iter__()方法,将迭代请求委托到对象内部持有的容器上。
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)
print(self._children)
def __iter__(self):
return iter(self._children)
root=Node(0)
child1=Node(1)
child2=Node(2)
root.add_child(child1)
root.add_child(child2)
for ch in root:
print(ch)
'''
结果:
[Node(1)]
[Node(1), Node(2)]
Node(1)
Node(2)
'''
切片我们不陌生,普通的切片操作不能对迭代器产生的数据做切片处理,itertools.islice()函数是完美的选择。
def Cutdown(n):
while n>0:
yield n
n-=1
#Cutdown(6)[1:2]#'generator' object is not subscriptable
import itertools
s=itertools.islice(Cutdown(6),1,3)#
注:islice()函数产生的数据和迭代器一样,只能访问一次,访问多次还需要转换成列表。
我们已经知道了普通切片操作无法对生成器使用,如果我需要跳过一部分元素,那应该如何操作呢?需求简单如跳过前N个元素可以使用islice(iter,N,None)的方法。需求复杂你可能会考虑过滤函数filter,其实我们还可以使用itertools.dropwhile(Lf,iterable)【函数参数前者为过滤函数,后者为可迭代对象】。
import itertools
def Cutdown(n):
while n>0:
yield str(n)
n-=1
def L(x):
if x>'2':
return x
for i in itertools.dropwhile(L ,Cutdown(8) ):#跳过x>2的数输出
print(i,end=',')#2,1
我们想对一系列元素所有可能的组合或排列进行迭代
以元组序列返回,元素全排列itertools.permutations(iterable,N)
元素的全部组合【不重复】itertools.combinations()
import itertools
items=['a','b','c']
for p in itertools.permutations(items,3):
print(p,end='')
#('a', 'b', 'c')('a', 'c', 'b')('b', 'a', 'c')('b', 'c', 'a')('c', 'a', 'b')('c', 'b', 'a')
for i in itertools.combinations(items,3):
print(i)#('a', 'b', 'c')
迭代序列但想记录序列中当前处理到的元素索引
使用enumerate(iter,start=0)
迭代序列但想记录序列中当前处理到的元素索引
使用enumerate(iter,start=0)
讨论:这种情况特别适合于跟踪记录文件中的行号。
补充:对于enumerate的使用
my_list1=[['a','b'],['c','d'],['e','f']]
for n ,[x,y] in enumerate(my_list1):
print(n,x,y)
'''
0 a b
1 c d
2 e f
'''
同时迭代zip函数
itertools.chain()依次迭代
heapq.merge()依次迭代但会进行排序输出
a=[1,2,3,4]
b=['one','two','three','four']
for Int,Str in zip(a,b):
print(Int,Str)
'''
1 one
2 two
3 three
4 four
'''
for i in itertools.chain(a,b):
print(i,end='')
'''1234onetwothreefour'''
内建函数iter()可以接受一个无参的可调用对象,以及一个哨兵值(结束值)作为输入。iter创建一个迭代器,重复调用用户提供可调用对象,直到它返回哨兵值为止。
with open('exhibitionism.txt','r') as f:
while True:
q=f.read(10)
if not q:
break
print(q)
#------------替换
with open('exhibitionism.txt','r') as f:
for chunk in iter(lambda:f.read(10),''):
print(chunk)
常对read\recv使用。
我们已经知道了next只能正序获取值,但是如果我们想要反向迭代的话,可利用reversed()函数实现反向迭代,但这个方法只有待处理的对象有可确定的大小或实现__reversed__()特殊方法时才能奏效。否则必须先将这个对象转换成列表,但当对象比较大时无疑会消耗大量的内存。
with open('exhibitionism.txt','r') as f:
for line in reversed(list(f)):#不使用list会TypeError: '_io.TextIOWrapper' object is not reversible
print(line)
下面实现__reversed__()方法的示例:
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 i in Countdown(10):
print(i,end='')#10987654321
for i in reversed(Countdown(10)):
print(i,end='')#12345678910
生成器是一种特殊的迭代器,它的返回值不是通过return而是用yield
生成器算得上是Python语言中最吸引人的特性之一,生成器其实是一种特殊的迭代器,不过这种迭代器更加优雅。它不需要再像上面的类一样写__iter__()和__next__()方法了,只需要一个yiled关键字。 生成器一定是迭代器(反之不成立)。
创建:
def countdown(n):
while n>0:
yield n
n-=1
print('Done')
q=countdown(2)
print(next(q))
print(q.__next__())
print(next(q))
'''
2
1
Done#StopIteration
'''
示例二
class NodeNoOne(Node):
def depth_first(self):
print('self:',self)
yield self
for c in self:
yield from c.depth_first()
root=NodeNoOne(0)
child1=NodeNoOne(1)
child2=NodeNoOne(2)
root.add_child(child1)
root.add_child(child2)
child1.add_child(NodeNoOne(3))
child1.add_child(NodeNoOne(4))
child2.add_child(NodeNoOne(5))
for ch in root.depth_first():
print(ch)
'''
self: Node(0)
Node(0)
self: Node(1)
Node(1)
self: Node(3)
Node(3)
self: Node(4)
Node(4)
self: Node(2)
Node(2)
self: Node(5)
Node(5)
'''
python生成器的一个重要特性就是利用next可以实现交互,即yield变成一个表达式,而值可以通过名为send的方法来传递值。
def psychologist():
print('Could you say something that make you unhappy to make us happy?')
while True:
answer=(yield )
if answer is not None:
if answer.endswith("?"):
print("不要问我,我只是可爱的兔兔")
elif answer.endswith("。"):
print("就这?就这?我还以为有多好笑呢?")
print("我来讲个给你听一听")
print("LSP是啥意思?答:Lovely Special Person 可爱又独特的人")
print('LSP,很好笑吧')
else:
print('大声点,听不见')
else:
print('额,没有吗?真是很好(违心的说)')
>>>free=psychologist()
>>>next(free)
Could you say something that make you unhappy to make us happy?
>>>free.send("I free bad 。")
就这?就这?我还以为有多好笑呢?
我来讲个给你听一听
LSP是啥意思?答:Lovely Special Person 可爱又独特的人
LSP,很好笑吧
说明:send的作用和next类似,但会将函数定义内部传入的值变成yield的返回值。
ContextManager ,上下文是 context 直译的叫法,在程序中用来表示代码执行过程中所处的前后环境。上下文管理器中有 enter 和 exit 两个方法。enter 方法会在执行 with 后面的语句时执行,一般用来处理操作前的内容。比如一些创建对象,初始化等;exit 方法会在 with 内的代码执行完毕后执行,一般用来处理一些善后收尾工作,比如文件的关闭,数据库的关闭。
关于上下文管理器的大部分内容,都在类、并发中都有讲到,如类中的实现上下文管理器协议,并发中的显式上锁。这里只补充为讲到的知识。
with B() as A ,C() as D:
pass
#等价于
with B() as A:
with C() as D:
pass
使用类似乎是实现python语言提供的任何协议最灵活的方式,但对于许多场景并不是最好的方式。标准库中新增了contextlib模块,提供与上下文管理器一起使用的辅助函数。它最有用的部分contextmanager装饰器。
contextmanager
def tag(name):
print("<%s>" % name)
yield
print("%s>" % name)
with tag("h1"):
print("hello")
print("world")
#上述代码执行结果为:
<h1>
hello
world
</h1>
代码的执行顺序是:
with语句首先执行yield之前的语句,因此打印出<h1>;yield调用会执行with语句内部的所有语句,因此打印出hello和world;
最后执行yield之后的语句,打印出</h1>。
如果一个对象没有实现上下文,我们就不能把它用于with语句。这个时候,可以用closing()来把该对象变为上下文对象。例如,用with语句使用urlopen()
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen('https://www.python.org')) as page:
for line in page:
print(line)
概念了解:
1、调用方:调用委派生成器的客户端(调用方)代码
2、委托生成器:包含yield from表达式的生成器函数
3、子生成器:yield from后面加的生成器函数
yield from 是在Python3.3才出现的语法。所以这个特性在Python2中是没有的。yield from 后面需要加的是可迭代对象,它可以是普通的可迭代对象,也可以是迭代器,甚至是生成器。yield from后面加上可迭代对象,他可以把可迭代对象里的每个元素一个一个的yield出来,对比yield来说代码更加简洁,结构更加清晰. yield from 后面加上一个生成器后,就实现了生成的嵌套。当然实现生成器的嵌套,并不是一定必须要使用yield from,而是使用yield from可以让我们避免让我们自己处理各种料想不到的异常,而让我们专注于业务代码的实现。如果自己用yield去实现,那只会加大代码的编写难度,降低开发效率,降低代码的可读性。
# 子生成器
def average_gen():
total = 0
count = 0
average = 0
while True:
new_num = yield average
if new_num is None:
break
count += 1
total += new_num
average = total/count
# 每一次return,都意味着当前协程结束。
return total,count,average
# 委托生成器
def proxy_gen():
while True:
# 只有子生成器要结束(return)了,yield from左边的变量才会被赋值,后面的代码才会执行。
total, count, average = yield from average_gen()
print("计算完毕!!\n总共传入 {} 个数值, 总和:{},平均数:{}".format(count, total, average))
# 调用方
def main():
calc_average = proxy_gen()
next(calc_average) # 预激协程
print(calc_average.send(10)) # 打印:10.0
print(calc_average.send(20)) # 打印:15.0
print(calc_average.send(30)) # 打印:20.0
calc_average.send(None) # 结束协程
# 如果此处再调用calc_average.send(10),由于上一协程已经结束,将重开一协程
if __name__ == '__main__':
main()
yield from它可以帮我们处理异常StopIteration
迭代协议:迭代协议具体分为两个协议:可迭代协议和迭代器协议。可迭代协议允许对象定义或定制它们的迭代行为,例如,在一个对象中,哪些值可以被遍历到。一些内置类型同时是内置可迭代对象,并且有默认的迭代行为,迭代器协议定义了产生一系列值(无论是有限个还是无限个)的标准方式。当值为有限个时,所有的值都被迭代完毕后,则会返回一个默认返回值。