11. 函数-三大器
一、迭代器
1. 定义
对于list、string、tuple、dict等这些容器对象,使用for循环遍历是很方便的。在后台for语句对容器对象调用iter()函数。iter()是python内置函数。iter()函数会返回一个定义了next()方法的迭代器对象,它在容器中逐个访问容器内的元素。next()也是python内置函数。在没有后续元素时,next()会抛出一个StopIteration异常,通知for语句循环结束。
迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。实际上,在使用next()函数的时候,调用的就是迭代器对象的_next_方法(Python3中是对象的_next_方法,Python2中是对象的next()方法)。所以,我们要想构造一个迭代器,就要实现它的_next_方法。但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现_iter_方法,而_iter_方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的_iter_方法返回自身self即可
2. 一些术语
- 迭代器协议:对象需要提供next()方法,它要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代。
- 可迭代对象:实现了迭代器协议对象。list、tuple、dict都是Iterable(可迭代对象),但不是Iterator(迭代器对象)。但可以使用内建函数iter() ,把这些都变成Iterable(可迭代器对象)。
- for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值给item,当遇到StopIteration的异常后循环结束
# 随便定义一个list
listArray=[1,2,3]
# 使用iter()函数
iterName=iter(listArray)
print(iterName)
结果如下:是一个列表list的迭代器
<list_iterator object at 0x0000017B0D984278>
print(next(iterName))
print(next(iterName))
print(next(iterName))
# 没有迭代到下一个元素,直接抛出异常
print(next(iterName))
# 1
# 2
# 3
# Traceback (most recent call last):
# File "Test07.py", line 32, in <module>
# StopIteration
# 实现斐波那契数列
class Fib(object):
def __init__(self, max):
super(Fib, self).__init__()
self.max = max
def __iter__(self):
self.a = 0
self.b = 1
return self
def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib
# 定义一个main函数,循环遍历每一个菲波那切数
def main():
# 20以内的数
fib = Fib(20)
for i in fib:
print(i)
# 测试
if __name__ == '__main__':
main()
3. 可迭代对象
list,tuple,set,dict,str等可以直接作用于for循环的对象,称为可迭代对象。可迭代对象实现了__iter__方法,用于返回迭代器。
demo = [1,2,3,4]
print(isinstance(demo, Iterable)) //True
iter_object = iter(demo)
print(iter_object) //<list_iterator object at 0x00000258DC5EF748>
4. 实现一个for循环
s = 'hello'
it = iter(s)
while (True):
try:
print(next(it))
except StopIteration:
del it
break
StopIteration异常将在迭代器耗尽后被抛出,for-in、生成式(comprehension)、元组解压(tuple unpacking)等迭代操作都会处理并这个异常。
迭代器是个迭代值生产工厂,它保存迭代状态,并通过next()函数产生下一个迭代值。实现迭代器需要实现以下两个方法:__iter__
返回self ;__next__
返回下一个可用的元素,如果无可用元素则抛出StopIteration异常
迭代器实现__iter__
,因此所有的迭代器都是可迭代的,下图展示了iterable和iterator的结构。
二、生成器
1. 定义
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator。
作用:延迟操作。也就是在需要的时候才产生结果,不是立即产生结果。
2. 示例
(1) 第一类
第一类:生成器函数:还是使用 def 定义函数,但是,使用yield而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次从它离开的地方继续执行
# 菲波那切数列
def Fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1
return '亲!没有数据了...'
# 调用方法,生成出10个数来
f=Fib(10)
# 使用一个循环捕获最后return 返回的值,保存在异常StopIteration的value中
while True:
try:
x=next(f)
print("f:",x)
except StopIteration as e:
print("生成器最后的返回值是:",e.value)
break
(2) 第二类
生成器表达式:类似于列表推导,只不过是把一对大括号[]
变换为一对小括号()
。但是,生成器表达式是按需产生一个生成器结果对象,要想拿到每一个元素,就需要循环遍历。
# 一个列表
xiaoke=[2,3,4,5]
# 生成器generator,类似于list,但是是把[]改为()
gen=(a for a in xiaoke)
for i in gen:
print(i)
结果是:
2
3
4
5
3. send()和__next__()
# send和__next__()⼀样都可以让⽣成器执⾏到下⼀个yield.
def eat():
print("我吃什么啊")
a = yield "馒头"
print("a=",a)
b = yield "烧饼"
print("b=",b)
c = yield "菜盒子"
print("c=",c)
yield "GAME OVER"
gen = eat() # 获取生成器
ret1 = gen.__next__()
print(ret1)
ret2 = gen.send("胡辣汤") # 给上一个yield传值,也就是a
print(ret2)
ret3 = gen.send("狗粮")
print(ret3)
ret4 = gen.send("猫粮")
print(ret4)
结果:
我吃什么啊
馒头
a= 胡辣汤
烧饼
b= 狗粮
菜盒子
c= 猫粮
GAME OVER
- send和next()都是让⽣成器向下走⼀次
- send可以给上⼀个yield的位置传递值, 不能给最后⼀个yield发送值. 在第⼀次执⾏⽣成器代码的时候不能使⽤send()
4. yield和return
yield是分段来执⾏⼀个 函数. return呢? 直接停⽌执⾏函数。种使⽤⽣成器. ⼀次就⼀个. ⽤多少⽣成多少. ⽣成器是⼀个⼀个的指向下⼀个. 不会回去, __next__()
到哪, 指针就指到哪⼉. 下⼀次继续获取指针指向的值.
5. 生成器迭代器
三、装饰器
1. 定义
装饰器(Decorator)从字面上理解,就是装饰对象的器件。可以在不修改原有代码的情况下,为被装饰的对象增加新的功能或者附加限制条件或者帮助输出。装饰器有很多种,有函数装饰器、类装饰器。它强调开放封闭原则,装饰器语法是将@装饰名放在被装饰对象上面。简单来说,可以把装饰器理解为一个包装函数的函数,它一般将传入的函数或者是类做一定的处理,返回修改之后的对象.所以,我们能够在不修改原函数的基础上,在执行原函数前后执行别的代码
装饰器的本质是一个python函数,其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值(return)也是一个函数对象。比如:插入日志、性能测试、事务处理、缓存、权限校验。比如说,有需要功能模块需要添加认证功能;那这时就可以使用装饰器实现。
2. 定义简单装饰器
(1) 代码示例
def outer(func):
def inner():
print('认证成功!')
result = func()
print('日志添加成功!')
return result
return inner
@outer
def f1():
print('业务部门 1 数据接口...')
结果:
认证成功!
业务部门1数据接口......
日志添加成功
(2) 代码分析
①程序开始运行,从上往下解释,读到 def outer(func):的时候,发现是一个“一等公民”函数,于是把函数体加载到内存里,然后过 ↓
②读到@outer时,程序被@语法糖吸引住了,知道这是个装饰器,按规矩要立即执行的,于是程序开始运行@后面那个名字outer所定义的函数
③程序返回到outer函数,开始执行装饰器的语法规则。规则是:被装饰的函数的名字会被当做参数传递给装饰函数。装饰函数执行它自己内部的代码后,会将它的返回值赋值给被装饰的函数。原来的f1函数被当做参数传递给了func,而f1这个函数名之后会指向inner函数。
④注意:@outer和@outer()有区别,没有括号时,outer函数依然会被执行,这和传统的用括号才能调用函数不同,需要特别注意!另外,是f1这个函数名(而不是f1()这样被调用后)当做参数传递给装饰函数outer,也就是:func = f1,@outer等于outer(f1),实际上传递了f1的函数体,而不是执行f1后的返回值。还有,outer函数return的是inner这个函数名,而不是inner()这样被调用后的返回值。
⑤.程序开始执行outer函数内部的内容,一开始它又碰到了一个函数inner,inner函数定义块被程序观察到后不会立刻执行,而是读入内存中(这是默认规则)
⑥再往下,碰到return inner,返回值是个函数名,并且这个函数名会被赋值给f1这个被装饰的函数,也就是f1 = inner。根据前面的知识,我们知道,此时f1函数被新的函数inner覆盖了(实际上是f1这个函数名更改成指向inner这个函数名指向的函数体内存地址,f1不再指向它原来的函数体的内存地址),再往后调用f1的时候将执行inner函数内的代码,而不是先前的函数体。那么先前的函数体去哪了?还记得我们将f1当做参数传递给func这个形参么?func这个变量保存了老的函数在内存中的地址,通过它就可以执行老的函数体,你能在inner函数里看到result = func()这句代码,它就是这么干的!
3、带有参数的装饰器
def outer(func):
def inner(*args,**kwargs):
print("认证成功!")
result = func(*args,**kwargs)
print("日志添加成功")
return result
return inner
@outer
def f1(name,age):
print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18)
4、一个函数被多个装饰器修饰
def outer1(func):
def inner(*args,**kwargs):
print("认证成功!")
result = func(*args,**kwargs)
print("日志添加成功")
return result
return inner
def outer2(func):
def inner(*args,**kwargs):
print("一条欢迎信息。。。")
result = func(*args,**kwargs)
print("一条欢送信息。。。")
return result
return inner
@outer1
@outer2
def f1(name,age):
print("%s 正在连接业务部门1数据接口......"%name)
# 调用方法
f1("jack",18)
执行结果:
认证成功!
一条欢迎信息。。。
jack 正在连接业务部门1数据接口......
一条欢送信息。。。
日志添加成功
5. 装饰器修复功能
# 可获取func1自己的函数名,即帮助文档
from functools import wraps
def timer(func):
@wraps(func)
def inner():
print(time.time())
ret = func() # 原来的函数
return ret
return inner
@timer # func1 = timer(func1)
def func1():
"""
func1 xxxx
:return:
"""
print('func1')
return 'func1的返回值'
print(func1.__name__)