迭代器、生成器与装饰器
迭代器
迭代器定义
迭代器的使用简化了循环程序的代码并可以节约内存,生成器的使用也可以节约大量的内存,特别是需要生成大量序列的对象时。迭代器 是一种可以从其中连续迭代的一个容器,如前文所述,所有的序列类型 都是可迭代的。
从代码的角度看,迭代器是实现了迭代器协议方法的对象或类。迭 代器协议方法主要是两个:
- __iter__();
- __next__()。
“__iter__()”方法返回对象本身,它是for语句使用迭代器的要求。
“__next__()”方法用于返回容器中下一个元素或数据。当容器中的 数据用尽时,应该引发Stoplteration异常。
任何一个类,只要它实现了或具有这两个方法,就可以称其为迭代 器,也可以说是可迭代的。
自定义迭代器
【实例9-1】自定义一个迭代器的演示实例,其代码如下:
class Mylterator: #自定义类迭代器类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 #引Stoplteration错误
else:
raise Stoplteration
if __name__ == '__main__':
myiter = Mylterator() #实例化迭代器Mylterator
for i in myiter: #遍历并输出值
print ('迭代的数据元素为:',i)
【代码说明】代码中首先定义了迭代器类Mylterator,在其构造方 法中,初始化两个私有的实例属性,用于产生序列和控制序列产生的最大值。该迭代器总是返回所给数的n次方,但其最大值超过xmax参数值 的大小时,就会引发Stoplteration错误,并结束遍历。最后,实例化迭代 器类,并遍历迭代器的值序列,同时进行输出。
从代码中可以看出:如果需要产生很大范围的数的序列,采用列表 或元组等进行一次性生成,则必须会占据大量的内存空间,对系统有很 大的“杀伤力”。而使用了迭代器,则可以每次调用时生成一个,显然可 以节约大量的内存空间。
注:迭代器类一定要在某个条件下引发Stoplteration错误,以结束遍 历循环,否则会产生死循环。
【运行效果】如图所示,初始化迭代器时使用了默认参数,遍 历得到的序列是2的n次方的值,最大值不超过100。
内置迭代器工具
Python语言中,已经内建了一个用于产生迭代器的函数iter(),另外 在标准库的itertools模块中还有丰富的迭代器工具,它们存在于itertools 模块中。
1.內建迭代器函数iter()
内建的iter()函数具有两种使用方式,其原型如下:
- iter (iterable)
- iter (callable, sentinel)
第一种原型中只有一个参数,要求参数为可迭代的类型,当然也可 以使用前文所说的各种序列类型。
第二种原型中具有两个参数,第一个参数是可调用类型,一般为函 数;第二个参数称为“哨兵”,即当第一个参数(函数)调用返回值等于 第二个参数的值时,迭代或遍历停止。
2.itertools模块中常用工具函数
itertools模块中提供了近二十个迭代器工具函数,主要有三类,常用的主要有:
无限迭代器:
- count (start, [step]) #从start开始,以step为步进行计数迭代
- cycle (seq) #无限循环迭代
- seq repeat (elem, [n]) #循环迭代elem
迭代短序列:
- chain (p, q, ...) #链接迭代(将p,q连接起来迭代,就像从一个
- compress (data, selectors) #依据selectors中的值选择迭代data序列中
- dropwhile (pred, seq) #当pred对序列元素处理结果为假时开始迭代s
- filterfalse (pred, seq) #当pred处理为假的元素
- takewhile (pred, seq) #与dropwhile相反
- tee (it, n) #将it重复n次进行迭代
- zip_longest (p,q,...)
组合迭代序列
- product (p, q,...[, n]) #迭代排列出所有的排列
- permutations (p, r) #迭代序列中r个元素的排列
- combinations (p, r) #迭代序列中r个元素的组合
注:使用无限迭代器时,必须有迭代退出的条件,否则会导致死循环。
生成器
使用生成器,可以生成一个值的序列用于迭代,并且这个值的序列 不是一次生成的,而是使用一个,再生成一个,的确可以使程序节约大量内存。
生成器的创建
生成器对象是通过使用yield关键字定义的函数对象,因此,生成器 也是一个函数。生成器用于生成一个值的序列,以便在迭代中使用。
【实例9-3】演示了自定义生成器函数及其使用的实例,其代 码如下:
def myYield(n): #定义一个生成器(函数)
while n>0:
print ("开始生成...:")
yield n #yield语句,用于返回给调用者其后表达式的值
print ("完成一次...:")
n -= 1
if __name__ == '__main__':
for i in myYield(4): #for语句遍历生成器
print ("遍历得到的值:",i)
print()
my_yield = myYield(3) #生成一个生成对象
print ('已经实例化生成器对象')
my_yield.__next__() #手工调用其特殊方法,获取序列中一个值
print ('第二次调用__next__()方法:')
my__yield.__next__()
【代码说明】代码自定义了一个递减数字序列的生成器,每次调用 时都会产生一个从调用时所提供值为初始值的不断递减的数字序列。生 成对象可以直接被for遍历,也可以手工进行遍历,手工的方法见上面代 码最后两行。
【运行效果】如图9.3所示,第一次使用for语句直接遍历自己创建 的生成器,第二次用手工的方法获取生成器产生的数值序列:
深入生成器
如上图所示,程序运行结果中的空行之后的输出“已经实例化生成 器对象”之前,已经实例化了生成器对象,但生成器并没有运行(没有 输出“开始生成”)。当第一次手工调用__next__()方法之后,才输出“开 始生成”,标志着生成器已经运行,而在输出“第二次调用__next__()方 法:”之前并没有输出“完成一次”,说明yield语句运行之后就立即停止 了。而第二次调用__next__()方法之后,才输出“完成一次…”,说明生 成器的恢复运行是从yield语句之后开始运行的。
yield语句不仅可以使函数成为生成器和返回值,还可以接受调用者 传来的数值。但值得注意的是:第一次调用生成器时不能传送给生成器 None以外的值,否则会引发错误。
生成器与协程
上节所述的运用send()方法来重置生成器的生成序列,其实也称为 协程。协程是一种解决程序并发的方法。
装饰器
装饰器是一种增加函数或类的功能的简单方法,它可以快速地给不 同的函数或类插入相同的功能。从本质上说,它是一种代码实现方式。
装饰器的表示语法是使用一个特殊的符号“@”来实现的。装饰器装 饰函数或类就是用“@装饰器名称”放在函数或类的定义行之前即可。例 如,有一个装饰器名称为disp_run_time,在要使用装饰器的函数定义时 使用如下形式:
@disp_run_time
def decorated__fun():
pass
使用了装饰器后,此处定义的函数decorated_fun()就可以只定义自 己所需的功能,而装饰器所定义的功能会自动插入到函数decorated_fun()之中。这样,可以节约大量具有相同功能的函数或类的 代码。即使是不同目的或不同类的函数或类也可以插入相同的功能。
装饰函数
【实例9-6】演示了自定义一个装饰器并用来装饰自定义的函 数,代码如下:
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()
hello ('John') #调用被装饰器装饰的函数
【代码说明】代码中首先定义了一个装饰器函数(abc),它带有 一个可以使用函数对象的参数。接着定义了两个被装饰器装饰的普通函 数(demo_decoration、hello)。最后对被装饰的函数进行调用。由此可 以看出,调用被装饰的函数,与调用普通函数没有任何区别。而在装饰 器定义的内部,很明显又定义了一个内嵌的函数wrapper(),在这个内嵌 的函数中执行了其他的一些语句,也调用了被装饰的函数。最后返回了 这个内嵌的函数,代替了被装饰的函数,从而完成了装饰器的功能。
【运行效果】如图所示,达到了在调用两个被装饰的函数前后 输出了相应的信息的功能。
装饰类
装饰器不仅可以装饰函数,也可以装饰类。定义装饰类的装饰器, 采用的方法是:定义内嵌类的函数,并返回新类。
【实例9-7】演示了一个类装饰器及其使用的例子,代码如下:
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() #调用方法position()
【代码说明】代码中首先定义了一个能够装饰类的装饰器abc;在其 中定义了一个内嵌类InnerClass用于代替被装饰的类,并返回新的内嵌 类。而在实例化普通类时,得到的就是被装饰器装饰后的类。
【运行效果】如图9.8所示,原来定义的坐标类只包含平面坐标, 而通过装饰器的装饰后,则成为了可以表示立体坐标的三个坐标值。在 图中可以看出显示的坐标为立体坐标值。