生成器、迭代器、装饰器
生成器(generator)
通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。
第一种generator,把一个列表生成式的[]
改成()
,就创建了一个generator:
>>> L = [x*x for x in range(5)] >>> L [0, 1, 4, 9, 16] >>> g = (x*x for x in range(5)) >>> g <generator object <genexpr> at 0x01BB55A0>
创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator,我们可以直接打印出list的每一个元素,打印generator只能获得一个内存地址,那我们要怎么获得值呢?
- 调用next()或者__next__(),next是内置函数,__next__()是generator方法,generator保存的是算法,每次调用
next(g)或g.__next__()
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。
>>> next(g) #等同于g.__next__() 0 >>> next(g) 1 >>> next(g) 4 >>> next(g) 9 >>> next(g) 16 >>> next(g) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
- 上面这种不断调用
next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象。
>>> g = (x*x for x in range(5)) >>> for i in g: ... print(i) ... 0 1 4 9 16
所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。
- 用捕捉异常的方式try ..except..else..也可以实现
g = (x*x for x in range(5)) while True: try: num = next(g) except StopIteration: break else: print(num) 结果: 0 1 4 9 16
第二种generator,如果推算的算法比较复杂,用类似列表生成式的for
循环无法实现的时候,还可以用函数来实现:
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:
1, 1, 2, 3, 5, 8, 13, 21, 34, ...
- 函数实现斐波拉契数列
def fib(n): i,a,b=0,1,1 while i<n: print(a) a,b = b,a+b #等同于t= (b,a+b),a = t[0] b = t[1] i+=1 fib(6) 结果: 1 1 2 3 5 8
- 上面的函数和generator仅一步之遥。要把
fib
函数变成generator,只需要把print(b)
改为yield b
就可以了:
def fib(n): i,a,b=0,1,1 while i<n: yield a a,b = b,a+b i+=1 return '超出'
这就是定义generator的另一种方法。如果一个函数定义中包含yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator:
f = fib(6) print(f) 结果: <generator object fib at 0x01265540>
generator和函数的执行流程不一样,函数是顺序执行,遇到return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。那怎么取得值呢?同样有三种方法,这里就不写next的实现方式了。
for i in fib(6): #for实现方式 print(i) 结果: 1 1 2 3 5 8 ------------------------------------------------------- f = fib(6) #捕捉异常实现方式 while True: try: x = f.__next__() except StopIteration as e: print("Generator return value %s"%e.value) break else: print(x) 结果: 1 1 2 3 5 8 Generator return value 超出
yield还可实现在单线程的情况下实现并发运算,详见代码
def customer(): while True: s = yield #等待请求 if isinstance(s,str):#接收到请求后,开始处理请求 print("字符串") elif isinstance(s,list): print("列表:%s"%s) def productor(): a = customer() a.__next__() #不掉用next,只是把函数变成一个生成器 a.send("你知道我是什么类型嘛?") #发送信息 a.send([1,2,3,4,5]) productor()
迭代器(iterator)
可以直接作用于for
循环的数据类型有以下几种:
一类是集合数据类型,如list
、tuple
、dict
、set
、str
等;
一类是generator
,包括生成器和带yield
的generator function。
这些可以直接作用于for
循环的对象统称为可迭代对象:Iterable
。
可以使用isinstance()
判断一个对象是否是Iterable
对象
>>> from collections import Iterable >>> isinstance([],Iterable) True >>> isinstance({},Iterable) True >>> isinstance(3,Iterable) False >>> isinstance('123',Iterable) True
>>> from collections import Iterator >>> isinstance([],Iterator) False >>> isinstance((x for x in range(5)),Iterator) True
Iterator
对象,但list
、dict
、str
虽然是Iterable
,却不是Iterator
。list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数>>> from collections import Iterator >>> isinstance([],Iterator) False >>> isinstance(iter([]),Iterator) True
装饰器(decorator)
先了解清除几个概念:什么是高阶函数?什么是嵌套函数?
- 高阶函数
变量可以指向函数,下面以内置函数求绝对值函数abs为例
>>> abs(-10) 10 >>> abs <built-in function abs> >>> f= abs #变量f指向函数abs >>> f <built-in function abs> >>> f(-10) #调用函数 10
说明变量f
现在已经指向了abs
函数本身。直接调用abs()
函数和调用变量f()
完全相同。
函数名也是变量
那么函数名是什么呢?函数名其实就是指向函数的变量!对于abs()
这个函数,完全可以把函数名abs
看成变量,它指向一个可以计算绝对值的函数!
如果把abs
指向其他对象,会有什么情况发生?
>>> abs = 10 >>> abs(-10) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable >>> abs 10
把abs
指向10
后,就无法通过abs(-10)
调用该函数了!因为abs
这个变量已经不指向求绝对值函数而是指向一个整数10
!
当然实际代码绝对不能这么写,这里是为了说明函数名也是变量。要恢复abs
函数,请重启Python交互环境。
注:由于abs
函数实际上是定义在import builtins
模块中的,所以要让修改abs
变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10
。
传入函数
既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。
def add(x,y,f): return f(x)+f(y) print(add(-1,3,abs)) 结果: 4
编写高阶函数,就是让函数的参数能够接收别的函数。
返回函数
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
我们来实现一个可变参数的求和。通常情况下,求和的函数是这样定义的:
def calc_sum(*args): ax = 0 for i in args: ax+=i return ax
但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数:
def lazy_sum(*args): def add(): ax = 0 for i in args: ax += i return ax return add
分别调用calc_sum(1,2,3,4,5)和lazy_sum(1,2,3,4,5)是什么结果呢?
print(calc_sum(1,2,3,4,5)) print(lazy_sum(1,2,3,4,5)) 结果: 15 <function lazy_sum.<locals>.add at 0x01062198>
calc_sum直接返回计算结果
lazy_sum返回嵌套函数add的内存地址,lazy_sum即嵌套函数,那要怎么得到结果呢?很简单,和调用函数的方式一样
print(lazy_sum(1,2,3,4,5)()) #15
请再注意一点,当我们调用lazy_sum()
时,每次调用都会返回一个新的函数,即使传入相同的参数:
f1 = lazy_sum(1,2,3,4,5) f2 = lazy_sum(1,2,3,4,5) print(f1) print(f2) 结果: <function lazy_sum.<locals>.add at 0x011D2198> <function lazy_sum.<locals>.add at 0x011D2108>
那装饰器是什么呢?
import time def timer(func): #传入函数 print("我进入timer函数了") def demo(): #嵌套函数 start_time = time.time() func() end_time = time.time() print("func time is %s"%(end_time-start_time)) return demo #返回函数 @timer #装饰器@timer相当于text = timer(text) def text(): #不加参数 time.sleep(1) print("in the text") text() 结果: 我进入timer函数了 in the text func time is 1.0075056552886963
当我们调用text()函数时,实际上运行了两步,第一、text = timer(text),此时text指向了demo,第二、调用text(),那实际上就是调用demo()
装饰器升级版:每个函数可能都有不同的参数,那我们要怎么实现呢?
import time def timer(func): def demo(*args,**kwargs): #传入非固定参数 start_time = time.time() func(*args,**kwargs) end_time = time.time() print("func time is %s"%(end_time-start_time)) return demo @timer def text(): #不加参数 time.sleep(1) print("in the text") @timer def text2(name): #加参数 time.sleep(1) print("in the text2!parameter is %s"%name) text() text2("lxj") 结果: in the text func time is 1.000171422958374 in the text2!parameter is lxj func time is 1.000110387802124
装饰器终极版,装饰器传入参数。认证用户登录登陆user和bbs需进行密码验证
user1,word1 = 'lxj','123' def my_key(key): def wrapper(func): def loggin(*args,**kwargs): if key == 'local': username = input("username:").strip() password = input("password:").strip() if username == user1 and password ==word1: func(*args,**kwargs) print("\033[32;1m验证成功\033[0m") else: print("\033[32;1m验证失败\033[0m") elif key =='lbdr': print("%s验证尚未激活该功能"%key) return loggin return wrapper def home(): print("welcome to homepage!") @my_key(key = 'local') def user(): print("welcome to personal page!") @my_key(key = 'lbdr') #装饰器带参数 def bbs(): print("welcome to bbs !") home() user() bbs() 结果: welcome to homepage! username:lxj password:123 welcome to personal page! 验证成功 lbdr验证尚未激活该功能
我们对调用uesr()进行剖析:当我们调用user时,实际上user = my_key('local')(user),首先是执行my_key('local'),返回函数wrapper,相当于指向wrapper,后面加(user),相当于调用wrapper(user),此时user指向了loggin,最后我们调用user()其实就是调用loggin()