Python学习(十)迭代器、生成器和装饰器
Chapter 7 迭代器、生成器和装饰器
迭代器、生成器和装饰器可以说是Python的三大法器,是Python的常用语法形式。下面分别来看一下各自的用途。
7.1 迭代器
7.1.1 迭代器概述
(1)迭代器简介
迭代器是一个数据流对象或容器,使用其中的数据时,每次从中取出一个数据,直至数据被取完。
迭代器能记住遍历位置的对象,只能往前遍历,保证数据不会被重复使用。优点是能简化循环程序的代码,并节约内存。
在1.4 Python控制语句 中介绍了for
语句的使用,实际上与迭代器的模式类似,按要求生成迭代对象并在for
语句中使用。
要区分两个概念,一个是可迭代的对象,一个是迭代器对象。
- 可迭代的(
Iterable
):可以用for
语句循环取出数据,例如list
、tuple
、dict
、set
、str
等 - 迭代器(
iterator
):依次迭代取出数据的对象,内存空间存储方式:<list_iterator object at 0x01E35770>
可以用isinstance()
判断对象是是否是Iterable
对象
>>> isinstance([],Iterable)
True
>>> isinstance({},Iterable)
True
>>> isinstance('abc',Iterable)
True
>>> isinstance((x for x in range(10)),Iterable)
True
>>> isinstance(100,Iterable)
False
(2)迭代器协议
迭代器主要有两个协议方法:__iter__
和 __next__
。任何一个类实现或具有这两种方法,就可以称之为迭代器。
__iter__
用于创建一个迭代器,返回对象本身,是for
语句使用迭代器的要求。__next__
用于返回迭代器中下一个元素,当数据用尽时会出现StopIteration异常。
7.1.2 创建和使用迭代器
以Fibonacci(斐波那契数列,递推公式为an+2=an+1+an)为例,创建 Fibonacci 迭代器对象:
# define a Fib class 定义迭代器类
class Fib(object):
def __init__(self, max): #定义构造方法
self.max = max #记录生成数列的个数
self.n, self.a, self.b = 0, 0, 1 #生成数列的索引n,记录数列前2个值a和b
def __iter__(self): #定义迭代器协议方法
return self #返回类的本身
def __next__(self): #定义迭代器协议方法
if self.n < self.max: #当索引小于数列个数
r = self.b #r记录数列第2个值
self.a, self.b = self.b, self.a + self.b #a1 = a2, a2 = a1 + a2
self.n = self.n + 1 #索引增加1
return r
raise StopIteration() #需要在某个条件下引发StopIteration错误,才能结束循环
# using Fib object 使用类
for i in Fib(5):
print(i)
将会输出前 5 个 Fibonacci 数据 1,1, 2, 3, 5
实现2个数累加的功能
7.1.3 内置迭代器方法iter()
(1)iter(iterable)
iter(iterable)
原型中只有一个参数,为可迭代的类型,如前文提到的list
、tuple
、dict
、set
、str
等。
>>> list=[1,2,3,4]
>>> it = iter(list)
>>> for i in it:
print(i)
1
2
3
4
可以将Iterable
类型的对象用iter()
转换为Iterator
迭代器
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True
(2)iter(callable,sentinel)
iter(callable,sentinel)
原型包含两个参数,callable
表示可调用类型,一般为函数;sentinel
是一个标记,当第一个参数的返回值等于第二个参数的值时,则停止迭代或遍历
>>> class Count: #定义类
def __init__(self,x=0): #定义构造方法
seld.x=x #初始化实例属性x
>>> count = Count() #实例化
def use_iter(): #定义用于iter()的函数
count.x += 2 #修改实例属性x的值
return count.x
>>> for i in iter(use_iter,12): #通过迭代方法iter()产生的迭代器
print(i)
2
4
6
8
10
7.1.4 方法next()
可迭代的对象Iterable
不可以使用next
方法,但迭代器Iterator
可以使用next
方法依次取出数据。因为迭代器是一个数据流,能够被调用并不断返回数据,直至出现 StopIteration
错误。
Iterator
的计算时惰性的,只有在需要返回下一个数据才会进行计算。
for
循环语句的本质就是通过不断调用next
函数实现。
for x in [1, 2, 3, 4, 5]:
pass
等价于
# 创建并获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环
while True:
try:
# 获得下一个值
x = next(it)
except StopIteration:
# 遇到StopIteration就退出循环
break
这里捕获StopIteration
错误的处理方法,会在后面详细介绍。
凡是可作用于for循环的对象都是Iterable类型
凡是可作用于next()函数的对象都是Iterator类型,表示一个惰性计算的序列
集合数据类型如list、dict、str等是Iterable但不是Iterator,可以通过iter()函数获得一个Iterator对象。
7.2 生成器
7.2.1 生成器概述
生成器(generator)可以生成值的序列用于迭代。由于值的序列是使用完一个再生成一个,能够使程序节省内存。
在Python中,使用了yield
关键字定义的函数称为生成器。yield
关键字能实现类似迭代的效果。每次调用生成器时,遇到yield
关键字函数就会暂停并保存当前状态信息,返回yield
的值,并在下一次执行next
方法时从当前位置继续执行。
因此,调用一个生成器函数,则返回一个迭代器对象。
生成器能够一边循环一边计算,可以得到庞大的数据;因为当前运行信息依然保留,便于下次循环调用,能够节省硬件资源。
7.2.2 创建和使用生成器
创建生成器有很多种方法。
(1)将列表[]
改成()
将列表生成式的[]
改成()
>>> List = [x * x for x in range(10)]
>>> List
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> generator = (x * x for x in range(10))
>>> generator
<generator object <genexpr> at 0x0000023130E6F048>
如上,List的最外层为[]
,而generator最外层为()
。我们可以通过调用list直接打印每个元素。要打印generator的每个元素,可以用next()
函数获得生成器的下一个返回值,直至报错。
>>> next(generator)
0
>>> next(generator)
1
>>> next(generator)
4
>>> next(generator)
9
>>> next(generator)
16
>>> next(generator)
25
>>> next(generator)
36
>>> next(generator)
49
>>> next(generator)
64
>>> next(generator)
81
>>> next(generator)
Traceback (most recent call last):
File "<pyshell#61>", line 1, in <module>
next(generator)
StopIteration
因为生成器保存的是算法而不是数据,每次调用next()
函数时才会计算下一个元素的值,直至计算到最后一个元素,并报错StopIteration
。
但是不断调用next()
函数会非常麻烦,一般会使用for
循环来进行数据输出。
>>> generator = (x * x for x in range(10))
>>> for n in generator:
print(n)
0
1
4
9
16
25
36
49
64
81
使用for
循环也能够避免调用next()
函数出现StopIteration
错误。
(2)函数包含yield
函数定义包含yield
关键字
在迭代器一节介绍了斐波拉契数列(Fibonacci)的代码,用迭代器类表示,也可以将其写成函数。
def fib(max):
n, a, b = 0, 0, 1
while n < max:
r = b
a, b = b, a+b
n = n + 1
yield r
其中赋值语句a, b = b, a+b
相当于
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]
但是不必显式写出临时变量t,相当于a=b b=a+b
for i in fib(5):
print(i)
1
1
2
3
5
输出前 5 个 Fibonacci 数据 1, 1, 2, 3, 5
与函数执行流程不同的是,函数为顺序执行,遇到return
语句或最后一行语句就会返回;而生成器执行时,每次调用next()
函数就会执行,遇到yield
语句就会返回,再次执行则从上次返回的yield
语句处继续执行。
7.2.3 send()方法
send()
方法可以重置生成器的生成序列,称为协程,能够解决程序并发和同步设计。send()
方法可以再次转入一个值。
下面以生产者和消费者的模型来介绍send()
方法的用途。
def consumer():
print('等待接收任务...')
while True:
data = (yield) #返回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
这里消费者模型consumer()
是一个生成器。调用生产者模型producer()
后,consumer()
先打印等待信息,后运行__next__()
返回迭代器下一个元素,每次循环后就利用send()
发送一个任务给consumer()
,生成器调用函数执行,返回yield
后面的值输出收到任务。这里运用yield
语句的hold特性完成任务。
注意生成器需要被挂起才能运行send()
,也就是说第一次要用next()
或send(None)
启动
7.3 装饰器
装饰器(Decorator)是一种增加函数或类的功能的简单方法,可以给不同函数或类插入相同功能。
7.3.1 装饰函数
装饰器可为不同的函数或类增加相同的功能,且不需要修改函数的定义,此时可使用装饰器,在代码运行期间动态增加功能。本质上装饰器是返回函数的高阶函数。
装饰器表示语法:用@装饰器名称
来实现。
(1)两层嵌套装饰器
定义一个now()
函数,输出当前日期。
def now():
print('2020-9-15')
调用
>>> f = now
>>> f()
2020-9-15
函数对象有一个__name_
_属性,可获取函数的名字:
>>> now.__name__
'now'
>>> f.__name__
'now'
现在想要增加now()
函数的功能,如在函数调用前后自动打印日志。
定义一个log()
函数。wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。
(详见1.5.2 参数类型-(3)可变参数(4)关键字参数)
# 参数为func,在log()内定义一个新的函数wrapper(),返回形参func
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
定义装饰器与定义函数在形式上基本一致,但装饰器函数的参数必须含有函数或类对象,然后再装饰器函数中重新定义新的函数或类。
将log()
作为decorator置于now()
函数的定义处:
@log
def now():
print('2020-9-15')
调用now()
函数,运行now()
函数本身并在运行now()
函数前打印一行日志
>>> now()
call now():
2020-9-15
把@log
放到now()
函数的定义处,相当于执行了语句:
now = log(now)
原来的now()
函数仍然存在,但是现在的now()
变量指向新的函数,调用now()
执行新函数,即在log()
函数中返回wrapper()
函数。
(2)三层嵌套装饰器
装饰器本身需要传入参数时是可以带参数的,同时使用时可嵌套装饰。编写一个高阶函数。
def log(text):
def decorator(func):
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
在now()
函数中使用装饰器:
# text的内容为execute
@log('execute')
def now():
print('2020-9-15')
执行结果
>>> now()
execute now():
2020-9-15
3层嵌套装饰器的效果如下
>>> now = log('execute')(now)
剖析上面的语句,首先执行log('execute')
,返回的是decorator
函数,再调用返回的函数,参数是now
函数,返回值最终是wrapper
函数。
(3)对象属性修改
由于对象含有__name__
等属性,但是经过装饰器装饰之后,函数的__name__
发生了改变。
>>> now.__name__
'wrapper'
__name__
已经从原来的’now’变成了’wrapper’。因为返回的'wrapper()
函数名为wrapper
,因此需要把原始函数的属性复制到wrapper()
函数中,否则依赖函数签名的代码执行就会出错。
通过Python内置的functools.wraps
模块可完成wrapper.__name__ = func.__name__
属性赋值的操作。因此完整的装饰器代码如下:
两层嵌套的装饰器:
import functools
def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
三层嵌套的装饰器(装饰器带参数):
import functools
def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print('%s %s():' % (text, func.__name__))
return func(*args, **kw)
return wrapper
return decorator
7.3.2 装饰类
装饰器可以装饰函数和类。定义装饰类的装饰器的方法:定义内嵌类的函数,并返回新类。
# 定义类装饰器
def decorator_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 # 返回新定义的类
@decorator_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() # 调用方法position()
输出
>>>
x axis: 0
y axis: 0
z axis: 0
====================================================================
Python学习的内容参考
《Python编程:从入门到实践》-[美] Eric Matthes
《21天学通PYTHON》
莫烦Python
廖雪峰的Python教程
等