一、生成器
本来我们可以通过列表生成直接生成一个列表,为什么要用生成器呢?我们都知道列表的数据会被全部加在到内存,然后可以通过下标进行访问数据,如果我们要的数据量非常大呢,岂不是很占用内存,所以我们需要生成器,生成器呢是我们需要哪个数据就会生成哪个数据,也就是懒加载,只有当我们使用数据的时候才会被生成,这样就是一边循环,一边计算数据,这个就叫做生成器:generator。但是只能按顺序生成,因为会根据上一步的返回进行生成,这样我们就可以节约内存了。
接下来我们看一个小列子:生成列表和生成器的创建区别,创建L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator
>>> l = [i*2 for i in range(10)] #直接生成列表,会把数据全部加载 >>> l [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] >>> g = (i*2 for i in range(10)) #生成一个生成器则会返回个内存地址,然后可以通过next方法一个一个的取值,如果很多后面的值不需要使用,我们就可以节约很多内存 >>> g <generator object <genexpr> at 0x0000010E2E6A5DB0> >>> next(g) 0 >>> next(g) 2 >>> next(g) 4 >>> next(g) 6 >>> next(g) 8 >>> next(g) 10>>> next(g)12>>> next(g)14>>> next(g)16>>> next(g)18>>> next(g) #每次调用next方法,就会计算出下一个元素的值,直到计算出最后一个就会跑出StopIteration的异常Traceback (most recent call last): File "<stdin>", line 1, in <module>StopIteration
当然除了用next获取生成器的值,我们还可以使用for循环来获取,所以我们不建议使用next方法,那个太麻烦了,使用for循环的话也不用担心取得元素超出范围抛出StopIteration异常:
>>> g = (x **2 for x in range(5)) >>> g <generator object <genexpr> at 0x0000010E2E6A5570> >>> for i in g: ... print(i) ... 0 1 4 9 16
当然除了使用类似于生成列表的方式生成生成器,我们还可以使用函数来生成,我们都知道斐波拉契数列,除了前面两个元素,每个值都是前两个的值相加得到。
>>> def fib(n): ... i,a,b = 0,0,1 ... while i<n: ... print(b) ... a,b = b,a+b ... i += 1 ... return "done" ... >>> fib(5) 1 1 2 3 5
如上就是斐波拉契数列的非递归式代码,那么我们如何把这个函数变成一个生成器呢,从代码看来,我们产生的数据都是b,所以我们只要在输出b的代码行稍加更改动就可以。
>>> def fib(n): ... i,a,b = 0,0,1 ... while i<n: ... #print(b) yield b #修改成yield b即可 ... a,b = b,a+b ... i += 1 ... return "done" ...>>> fib(5)<generator object fib at 0x0000010E2E6A5D00
就是这么神奇,这就是generator的另一种生成方法,如果一个函数中包含yield关键字,那么函数不再是一个普通的函数了,而是一个generator,不考虑多线程,我们都知道函数是按顺序执行的,但是generator是不一样的,在每次调用next()的时候,遇到yield就会返回,再一次执行从上次返回的yield语句处执行。
def fib(n): i, a, b = 0, 0, 1 while(i<n): yield b a, b = b, a+b i += 1 return ‘done‘ data = fib(5) print(data) print(data.__next__()) print(data.__next__()) print("我来打断一下") print(data.__next__()) #结果如下 <generator object fib at 0x000002766915D6D0> 1 1 我来打断一下 2
用for循环调用generator时,我们无法获取generator的返回值,如果想要获取到返回值,则要捕获StopIteration异常,返回值包含在StopIteration的value中
data = fib(5) while True: try: x = next(data) print("data=",x) except StopIteration as e: print("generator return value:",e.value) break #程序结果是 data= 1 data= 1 data= 2 data= 3 data= 5 generator return value: done #这样我们就可以获取到这个返回值
二、装饰器
接下来我们聊聊装饰器,从字面意思来看是一个装饰的效果,在java中也有装饰模式,简单来说装饰器就是用来给函数添加新功能的,也就是函数功能的强化器,但是,装饰器在装饰函数的时候需要遵循几个原则:
1、不能改变原函数的代码
2、不能改变原函数的调用方式
3、不能改变原函数原有的结果
在聊装饰器前我们简单了解下高阶函数和嵌套函数:
那什么是高阶函数呢?我们可以这么理解,就是把一个函数a作为另一个函数b的参数进行传递,把函数当做变量来传递(我们可以理解函数即‘变量’),则函数b则是高阶函数:
def func(x, y, f): print(f(x)+f(y)) func(-3,9,abs) #执行结果 12
那什么是嵌套函数呢?嵌套函数并不是一个函数里面调用另一个函数,而是在定义函数的时候在其里面再定义函数的形式:
def func(x): def func1(y): print(y) func1(x) func(4) #执行结果是 4
好了,现在进去主题,聊聊我们的装饰器,来看一段代码(使用高阶函数)
import time def bar(): #原函数,也就是要被装饰的函数 time.sleep(1) print(‘in the bar‘) def test(f): start_time=time.time() f() stop_time=time.time() print("the f run time is %s" %(stop_time-start_time)) bar() print("分割线".center(50,‘-‘)) test(bar) #程序结果如下 in the bar -----------------------分割线------------------------ in the bar the f run time is 1.0021047592163086 Process finished with exit code 0
从上面结果看来,我们确实增强了函数bar的功能,但是不符合装饰的一个原则,改变了调用方式,接下来我们处理这个问题。当然我们也可以使用bar=test(bar)的方式重新给了bar函数,也可以通过bar()的方式调用,也能达到了装饰的效果,但是这样我们没法传参数,接下来我们看一段代码(嵌套函数出场了)
import time#其实就是利用嵌套函数+高阶函数连用 def timer(func): #timer(test1) func=test1 def deco(*args,**kwargs): #通过可变参数解决相对应的原函数传来的参数问题,这样就很灵活 start_time=time.time() func(*args,**kwargs) #run test1() stop_time = time.time() print("the func run time is %s" %(stop_time-start_time)) return deco @timer #这个相当于test1=timer(test1) def test1(): time.sleep(1) print(‘in the test1‘) @timer # test2 = timer(test2) = deco test2(name) =deco(name) def test2(name,age): print("test2:",name,age) test1() test2("HZhuizai",22) #程序执行结果如下 in the test1 the func run time is 1.0001072883605957 test2: HZhuizai 22 the func run time is 0.0
是不是这个装饰器很完美了,符合了三个原则,但是如果我们有一个需求就是对不同的两个函数有不一样的装饰呢,那我们该怎么实现呢?
import time user,passwd = ‘alex‘,‘abc123‘ def auth(auth_type): def outer_wrapper(func): def wrapper(*args, **kwargs): print("wrapper func args:", *args, **kwargs) func() if auth_type == "local": print("local 模式验证") elif auth_type == "ldap": print("ldap 模式验证") return wrapper return outer_wrapper @auth(auth_type="local") # home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type="ldap") def bbs(): print("welcome to bbs page") home() #wrapper() print("分隔符".center(50,‘-‘)) bbs() #程序执行结果 wrapper func args:welcome to home pagelocal 模式验证-----------------------分隔符------------------------wrapper func args:welcome to bbs pageldap 模式验证
为了能够通过装饰器传递参数,我们又在外层套了一个函数,可以归纳出装饰就是高阶函数和嵌套函数的组合,第一层为装饰次传递参数,第二层是高阶函数,把要装饰的函数作为变量传递进去,第三层则是我们原函数的参数,这样我们能完美的装饰原函数,挺高函数的功能。
三、迭代器
我们知道可以作用于for循环的数据有列表、字典、元组、集合、字符串,这些都是集合类型,当然还有我们的生成器也可以作用于for循环,这些可以直接作用于for循环的对象统称为迭代对象:Iterable,我们可以使用isinstance()判断一个对象是否是Iterable对象。
>>> from collections import Iterable >>> isinstance([],Iterable) True >>> isinstance("",Iterable) True
可以被next()函数调用并且不断返回下一个值的对象为迭代器:Iterator,同样可以使用isinstance()判断一个对象是否是Iterator对象。
>>> from collections import Iterator >>> isinstance((x for x in range(10)), Iterator) True >>> isinstance([x for x in range(10)], Iterator) False
列表、字典、元组、集合、字符串都是Iterable对象,我们可以通过iter()方法把Iterable对象转换成Iterator对象
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter(‘abc‘), Iterator) True
Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
归纳下:
凡是可作用于for
循环的对象都是Iterable
类型;
凡是可作用于next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。