第9章 迭代器、生成器与装饰器
迭代器、生成器与装饰器是Python语言中常用的语法形式。
迭代器的使用简化了循环程序的代码并可以节约内存,生成器的使用也可以节约大量的内存,特别是需要生成大量序列的对象时。迭代器是一种可以从其中连续迭代的一个容器,如前文所述,所有的序列类型都是可迭代的。而生成器则是函数中包含yield语句的一类特殊的函数。
装饰器的灵活性很强,可以为一个对象添加新的功能或者给函数插入相关的功能。在具体的程序设计中,可以灵活地设计出所需要的功能。
9.1 迭代器
Python中迭代器的使用是最为广泛的,本章以前代码中,凡是使用for语句,其本质上都迭代器的应用,本节主要介绍迭代器的概念、创建与应用。
9.1.1 迭代器概述
迭代器从表面上看是一个数据流对象或容器,当使用其中的数据时,每次从数据流中取一个数据,直到数据被取完,而且数据不会被重复使用。
从代码的角度看,迭代器是实现了迭代器协议方法的对象或类。迭代器协议方法主要是两个:
9.1.2 自定义迭代器
>>> class Mylterator:
... def __init__(self,x=2,xmax=100):
... self.__mul,self.__x= x,x
... self.__xmax = xmax
... def __iter__(self):
... return self
... def __next__(self):
... if self.__x and self.__x != 1:
... self.__mul *= self.__x
... if self.__mul <= self.__xmax:
... return self.__mul
... else:
... raise Stoplteration
... else:
... raise Stoplteration
...
>>> if __name__ == '__main__':
... myiter = Mylterator()
... for i in myiter:
... print('迭代的数据元素为:', i)
...
迭代的数据元素为: 4
迭代的数据元素为: 8
迭代的数据元素为: 16
迭代的数据元素为: 32
迭代的数据元素为: 64
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
File "<stdin>", line 13, in __next__
NameError: name 'Stoplteration' is not defined
>>>
9.1.3 内置迭代器工具
Python语言中,已经内建了一个用于产生迭代器的函数iter(),另外在标准库的itertools模块中还有丰富的迭代器工具,它们存在于itertools模块中。
>>> class Counter:
... def __init__(self,x=0):
... self.x = x
...
>>> counter = Counter()
>>> def used_iter():
... counter.x += 2
... return counter.x
...
>>> for i in iter(used_iter,8):
... print('本次遍历的数值', i)
...
本次遍历的数值 2
本次遍历的数值 4
本次遍历的数值 6
>>>
>>> import itertools
>>> for i in itertools.count(1, 3):
... print(i)
... if i >= 10:
... break
...
1
4
7
10
>>> x = 0
>>> for i in itertools.cycle(['a', 'b']):
... print(i)
... x += 1
... if x >= 6:
... break
...
a
b
a
b
a
b
>>> list(itertools.repeat(3, 3))
[3, 3, 3]
>>> list(itertools.chain([1,3],[2,3]))
[1, 3, 2, 3]
>>> list(itertools.compress([1,2,3,4],[1,[],True,3]))
[1, 3, 4]
>>> list(itertools.dropwhile(lambda x:x>6, [8, 9, 1, 2, 8, 9]))
[1, 2, 8, 9]
>>> list(itertools.takewhile(lambda x:x>10, [18, 19, 1, 21, 8, 9]))
[18, 19]
>>> for its in itertools.tee([0,1],2):
... for it in its:
... print(it)
...
0
1
0
1
>>>
9.2 生成器
使用生成器,可以生成一个值的序列用于迭代,并且这个值的序列不是一次生成的,而是使用一个,再生成一个,的确可以使程序节约大量内存。
9.2.1 生成器创建
>>> def myYield(n): #定义一个生成器(函数)
... while n>0:
... print("开始生成...:")
... yield n #yield语句,用于返回给调用者其后表达式的值
... print("完成一次....:")
... n -= 1
...
>>> if __name__ == '__main__':
... for i in myYield(4): #for语句遍历生成器
... print("遍历得到的值",i)
...
开始生成...:
遍历得到的值 4
完成一次....:
开始生成...:
遍历得到的值 3
完成一次....:
开始生成...:
遍历得到的值 2
完成一次....:
开始生成...:
遍历得到的值 1
完成一次....:
>>> print()
>>> my_yield = myYield(3) #生成一个生成对象
>>> print('已经实例化生成器对象')
已经实例化生成器对象
>>> my_yield.__next__() #手工调用其特殊方法,获取序列中一个值
开始生成...:
3
>>> print('第二次调用__next__()方法: ')
第二次调用__next__()方法:
>>> my_yield.__next__()
完成一次....:
开始生成...:
2
>>>
【代码说明】代码自定义了一个递减数字序列的生成器,每次调用时都会产生一个从调用时所提供值为初始值的不断递减的数字序列。生成对象可以直接被for遍历,也可以手工进行遍历,手工的方法见上面代码最后两行。
9.2.2 深入生成器
如上节所述,生成器中包含yield语句,可以用for直接遍历;也可以手工调用其__next__()方法进行遍历。
yield语句是生成器中的关键语句,生成器在实例化时并不会立即执行,而是等待调用其__next__()方法才开始运行。并且当程序运行完yield语句后就会“吼(hold)住”,即保持其当前状态且停止运行,等待下一次遍历时才恢复运行。
如图9.3所示,程序运行结果中的空行之后的输出“已经实例化生成器对象”之前,已经实例化了生成器对象,但生成器并没有运行(没有输出“开始生成”)。当第一次手工调用__next__()方法之后,才输出“开始生成”,标志着生成器已经运行,而在输出“第二次调用__next__()方法:”之前并没有输出“完成一次”,说明yield语句运行之后就立即停止了。而第二次调用__next__()方法之后,才输出“完成一次…”,说明生成器的恢复运行是从yield语句之后开始运行的。(???)
yield语句不仅可以使函数成为生成器和返回值,还可以接受调用者传来的数值。但值得注意的是:第一次调用生成器时不能传送给生成器None以外的值,否则会引发错误。(???)
>>> def myYield(n):
... while n>0:
... rcv = yield n
... n -= 1
... if rcv is not None:
... n = rcv
...
>>> if __name__ == '__main__':
... my_yield = myYield(3)
... print(my_yield.__next__())
... print(my_yield.__next__())
... print('传给生成器一个值, 重新初始化生成器。')
... print(my_yield.send(10))
... print(my_yield.__next__())
...
3
2
传给生成器一个值, 重新初始化生成器。
10
9
>>>
9.2.3 生成器与协程
上节所述的运用send()方法来重置生成器的生成序列,其实也称为协程。协程是一种解决程序并发的方法。
如果采用一般的方法来实现生产者与消费者这个传统的并发与同步程序设计问题,则要考虑的问题还是比较繁杂的。但通过生成器实现的协程,解决这个问题就很简单。
>>> def consumer():
... print('等待接收处理任务...')
... while True:
... data = (yield)
... print('收到任务: ', data)
...
>>> def producer():
... c = consumer()
... c.__next__()
... for i in range(3):
... print('发送一个任务...','任务%d' % i)
... c.send('任务%d' % i)
...
>>> if __name__ == '__main__':
... producer()
...
等待接收处理任务...
发送一个任务... 任务0
收到任务: 任务0
发送一个任务... 任务1
收到任务: 任务1
发送一个任务... 任务2
收到任务: 任务2
>>>
9.3 装饰器
装饰器是一种增加函数或类的功能的简单方法,它可以快速地给不同的函数或类插入相同的功能。从本质上说,它是一种代码实现方式。
9.3.1 装饰器概述
为了给不同的函数或类插入相同的功能,在Python中可以使用非常优雅而简单的工具——装饰器。与其他高级语言相比,简化了代码,并且可以快速地实现所需要的功能。同时,它为函数或类对象增加功能也是透明的,对于同一函数,既可以添加简单的功能,也可以添加复杂功能,运用起来很灵活。而在调用被装饰的函数时,没有任何附加的东西,仍然像调用原函数或没有被装饰的函数一样。
9.3.2 装饰函数
用装饰器装饰函数,首先要定义装饰器,然后用定义的装饰器来装饰函数。
>>> def abc(fun): #定义一个装饰器abc
... def wrapper(*args, **kwargs): #定义包装器函数
... print('开始运行...')
... fun(*args, **kwargs) #调用被装饰函数
... print('运行结束!')
... return wrapper #返回包装器函数
...
>>> @abc #装饰函数语句
... def demo_decoration(x): #定义普通函数,被装饰器装饰
... a = []
... for i in range(x):
... a.append(i)
... print(a)
...
>>> @abc
... def hello(name): #定义普通函数(被装饰器装饰)
... print('Hello',name)
...
>>> if __name__ == '__main__':
... demo_decoration(5) #调用被装饰器装饰的函数
... print()
...
开始运行...
[0, 1, 2, 3, 4]
运行结束!
>>> if __name__ == '__main__':
... demo_decoration(5)
... print()
... hello('John') #调用被装饰器装饰的函数
...
开始运行...
[0, 1, 2, 3, 4]
运行结束!
开始运行...
Hello John
运行结束!
>>>
9.3.3 装饰类
>>> def abc(myclass): #定义类装饰器
... class InnerClass: #定义内嵌类
... def __init__(self, z=0):
... self.z = 0
... self.wrapper = myclass() #实例化被装饰的类
... def position(self):
... self.wrapper.position()
... print('z axis:', self.z)
... return InnerClass #返回新定义的类
...
...
>>> @abc
... class coordination: #定义普通的类
... def __init__(self, x=0, y=0):
... self.x = x
... self.y = y
... def position(self):
... print('x axis:', self.x)
... print('y axis:', self.y)
...
>>> if __name__ == '__main__':
... coor = coordination() #实例化被装饰的类
... coor.position()
...
x axis: 0
y axis: 0
z axis: 0
>>>
9.4 小结