Python 迭代器+生成器+装饰器
一、迭代器
1、迭代器协议
- 迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个stoplteration异常,以终止迭代(只能 往后走,不能往前退)
- 协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象
2、可迭代对象
可迭代对象泛指一类对象,不是指的每一种对象,确切的说满足以下的条件的对象可以成为可迭代对象:
- 对象实现了__iter__方法
- __iter__方法返回了一个迭代器对象
我们比较容易理解的可迭代对象,比如说可以用for语句去遍历,实际for语句的内部实现应该就是首先调用对象的__iter__方法,获取一个迭代器对象,接着不停的调用迭代器对象的__next__方法,循环遍历取值。
3、迭代器对象(迭代器)
迭代器协议包括这些条件:
- 对象实现了__next__方法
- __next__方法返回了某个数值(当然一般情况下,我们需要的是返回这个对象的特定的数字,并且按照一定的顺序进行依次返回)
- __next__方法需要在值取完的时候,抛出StopIteration的错误信息。
能够迭代的类型:list tuple string set dict bytes迭代器有两个基本的方法:iter() 和 next()
- iter返回迭代器对象本身。这用于for 和in语句。
- next方法返回迭代器中的下一个值。如果没有更多的项目要返回,那么它应该引发StopIteration异常。
创建一个返回数字的迭代器,初始值为 1,逐步递增 1:
class MyNumbers: def __iter__(self): self.a = 1 return self def __next__(self): x = self.a self.a += 1 return x myclass = MyNumbers() myiter = iter(myclass) print(next(myiter)) print(next(myiter)) print(next(myiter)) print(next(myiter)) print(next(myiter))
输出:
1 2 3 4 5
二、生成器
可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己的内置的__iter__方法),所以生成器就是可迭代对象
1、生成器函数
常规函数的定义,但是,使用yield语句而不是return语句返回结果。yield语句语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
2、生成器表达式
类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
3、生成器的特点
生成器最大的特点是:边迭代 边输出
Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生产层器的主要好处。
这里举一个例子来说明生成器的优点
输出斐波那契数列前 N 个数
def fab(max): n, a, b = 0, 0, 1 L = [] while n < max: L.append(b) a, b = b, a + b n = n + 1 return L for n in fab(5): print n
该函数在运行中占用的内存会随着参数 max 的增大而增大,如果要控制内存占用,最好不要用 List来保存中间结果,而是通过 iterable 对象来迭代。
class Fab(object): def __init__(self, max): self.max = max self.n, self.a, self.b = 0, 0, 1 def __iter__(self): return self def next(self): if self.n < self.max: r = self.b self.a, self.b = self.b, self.a + self.b self.n = self.n + 1 return r raise StopIteration() for n in Fab(5): print n
Fab 类通过 next() 不断返回数列的下一个数,内存占用始终为常数。
然而,使用 class 改写的这个版本,代码远远没有第一版的 fab 函数来得简洁。如果我们想要保持第一版 fab 函数的简洁性,同时又要获得 iterable 的效果,yield 就派上用场了:
def fab(max): n, a, b = 0, 0, 1 while n < max: yield b # 使用 yield # print b a, b = b, a + b n = n + 1 for n in fab(5): print n
简单地讲,yield 的作用就是把一个函数变成一个 generator,带有 yield 的函数不再是一个普通函数,Python 解释器会将其视为一个 generator,调用 fab(i) 不会执行 fab 函数,而是返回一个 iterable 对象!在 for 循环执行时,每次循环都会执行 fab 函数内部的代码,执行到 yield b 时,fab 函数就返回一个迭代值,下次迭代时,代码从 yield b 的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到 yield。
一个带有 yield 的函数就是一个 generator,它和普通函数不同,生成一个 generator 看起来像函数调用,但不会执行任何函数代码,直到对其调用 next()(在 for 循环中会自动调用 next())才开始执行。虽然执行流程仍按函数的流程执行,但每执行到一个 yield 语句就会中断,并返回一个迭代值,下次执行时从 yield 的下一个语句继续执行。看起来就好像一个函数在正常执行的过程中被 yield 中断了数次,每次中断都会通过 yield 返回当前的迭代值。
yield 的好处是显而易见的,把一个函数改写为一个 generator 就获得了迭代能力,比起用类的实例保存状态来计算下一个 next() 的值,不仅代码简洁,而且执行流程异常清晰。
这里记录一个引出本篇博客的问题:
Scrapy中yield的理解
https://www.oschina.net/question/2254016_238539
三、装饰器
1、闭包
正式讲装饰器之前,我们要先插入闭包这一个概念。最早在《离散数学》和《编译原理》课程上应该接触过闭包的概念,但和这里python语言上的闭包却关系不大。
闭包就是一个函数定义中引用了函数外定义的变量,并且该函数可以在其定义环境外被执行。这样的一个函数我们称之为闭包。实际上闭包可以看做一种更加广义的函数概念。因为其已经不再是传统意义上定义的函数。
关于闭包的理解,参考博客:
https://blog.csdn.net/sc_lilei/article/details/80464645
https://www.cnblogs.com/yssjun/p/9887239.html
构成闭包有三个条件:
- 必须有一个内嵌函数
- 内嵌函数必须引用外部函数中的变量
- 外部函数的返回值必须是内嵌函数
闭包例子
# ENV>>> Python 3.6 # NO.1 def line_conf(a, b): def line(x): return a * x + b return line # NO.2 def line_conf(): a = 1 b = 2 def line(x): print(a * x + b) return line # NO.3 def _line_(a, b): def line_c(c): def line(x): return a * (x ** 2) + b * x + c return line return line_c
闭包可以避免全局变量的使用以及提供某种形式数据的隐藏。闭包内的闭包函数私有化了变量,完成了数据的封装。当函数中的变量和函数较少且其中某个功能常用时,使用闭包来封装。当变量和函数更加复杂时,则使用类来实现。
2、装饰器概念
先了解一下设计模式中的装饰模式https://www.cnblogs.com/wkfvawl/p/12671384.html
装饰模式的出现是为了解决动态扩展问题而生的,装饰器的出现也是如此。
参考:https://www.cnblogs.com/lianyingteng/p/7743876.html
python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被修饰函数。从上面这段描述中我们需要记住装饰器的几点属性,以便后面能更好的理解:
- 实质: 是一个函数
- 参数:是你要装饰的函数名(并非函数调用)
- 返回:是装饰完的函数名(也非函数调用)
- 作用:为已经存在的对象添加额外的功能
- 特点:不需要对对象做任何的代码上的变动
python装饰器有很多经典的应用场景,比如:插入日志、性能测试、事务处理、权限校验等。装饰器是解决这类问题的绝佳设计。并且从引入中的列子中我们也可以归纳出:装饰器最大的作用就是对于我们已经写好的程序,我们可以抽离出一些雷同的代码组建多个特定功能的装饰器,这样我们就可以针对不同的需求去使用特定的装饰器,这时因为源码去除了大量泛化的内容而使得源码具有更加清晰的逻辑。
通用模板:
// 通用模式 def mydecorator(func): def wrapped(*args, **kwargs): # 在函数调用前作点什么 result = func(*args, **kwargs) # 在函数调用后,做点什么 # 返回结果 return result return wrapped
3、函数装饰器
函数的函数装饰器
我们还是以为函数添加计时功能为例,讲述函数装饰器。
import time
def decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
func()
end_time = time.time()
print(end_time - start_time)
return wrapper
@decorator
def func():
time.sleep(0.8)
func() # 函数调用
# 输出:0.800644397735595
在上面代码中 func是我要装饰器的函数,我想用装饰器显示func函数运行的时间。@decorator这个语法相当于 执行 func = decorator(func),为func函数装饰并返回。在来看一下我们的装饰器函数 - decorator,该函数的传入参数是func (被装饰函数),返回参数是内层函数。简言之,外部函数传入被装饰函数名,内部函数返回装饰函数名。
这里的内层函数-wrapper,其实就相当于闭包函数,它起到装饰给定函数的作用,wrapper参数为*args, **kwargs。*args表示的参数以列表的形式传入;**kwargs表示的参数以字典的形式传入:
从图中我们可以看到:凡是以key=value形式的参数均存在kwargs中,剩下的所有参数都以列表的形式存于args中。这里要注意的是:为了不破坏原函数的逻辑,我们要保证内层函数wrapper和被装饰函数func的传入参数和返回值类型必须保持一致。
类方法的函数装饰器
类方法的函数装饰器和函数的函数装饰器类似。
import time
def decorator(func):
def wrapper(me_instance):
start_time = time.time()
func(me_instance)
end_time = time.time()
print(end_time - start_time)
return wrapper
class Method(object):
@decorator
def func(self):
time.sleep(0.8)
p1 = Method()
p1.func() # 函数调用
对于类方法来说,都会有一个默认的参数self,它实际表示的是类的一个实例,所以在装饰器的内部函数wrapper也要传入一个参数 - me_instance就表示将类的实例p1传给wrapper,其他的用法都和函数装饰器相同。
4、类装饰器
前面我们提到的都是让 函数作为装饰器去装饰其他的函数或者方法,那么可不可以让 一个类发挥装饰器的作用呢?答案肯定是可以的,一切皆对象嚒,函数和类本质没有什么不一样。类的装饰器是什么样子的呢?
class Decorator(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("decorator start")
self.f()
print("decorator end")
@Decorator
def func():
print("func")
func()
这里有注意的是:__call__()是一个特殊方法,它可将一个类实例变成一个可调用对象:
基于类装饰器的实现,必须实现**_call_和_init_**两个内置的函数。
- _init_:接受被装饰函数。
- _call_:实现装饰逻辑。
p = Decorator(func) # p是类Decorator的一个实例
p() # 实现了__call__()方法后,p可以被调用
要使用类装饰器必须实现类中的__call__()方法,就相当于将实例变成了一个方法。
5、装饰器链
一个python函数也可以被多个装饰器修饰,要是有多个装饰器时,这些装饰器的执行顺序是怎么样的呢?
可见,多个装饰器的执行顺序:是从近到远依次执行。