本节内容
- 装饰器
- 迭代器
- 生产器
- 序列化与反序列化
1、装饰器
装饰器的本质是函数,目的是为(装饰其他的函数)为其它函数添加附加功能。
原则:
1、不能修改本装饰的函数的源代码
2、不能修改被装饰的函数的调用方式
实现装饰器的知识储备:
1、函数即“变量”
1 def bar(): 2 print('in the bar') 3 def foo(): 4 print('in the foo') 5 bar() 6 7 foo() 8 # 输出 9 in the foo 10 in the bar 11 12 13 def foo(): 14 print('in the foo') 15 bar() 16 def bar(): 17 print('in the bar') 18 19 foo() 20 # 输出 21 in the foo 22 in the bar 23 24 25 def foo(): 26 print('in the foo') 27 bar() 28 29 foo() 30 31 def bar(): 32 print('in the bar') 33 34 # 输出 35 in the foo 36 NameError: name 'bar' is not defined #出现报错
上述代码中,bar和foo表示函数名,相当于函数变量的门牌号,每个门牌号对应一个内存地址,地址里面包含了对应的函数内容。
bar()和foo()表示执行函数,执行函数的过程就是先找到函数名(门牌号),然后通过门牌号找到其对应的内存地址并执行地址中的函数内容。
所以当我们在执行foo()的过程中,首先要满足函数已经被定义,即存在,并存入了相应的内存地址,准备被调用。所以找到内存地址中对应的函数内容:
print('in the foo')
bar()
会调用,并依次执行。此时,会先打印出,内容in the foo;其次,会执行bar(),这时就需要函数bar已存在,如果在执行bar()时函数还未生成,则会出现报错。
这也是为什么第一组和第二组中foo()可以成功执行,而第三组不能执行。因为在执行foo()的过程中,调用内容的过程中,bar()对应的函数bar还未被定义。
另:插播一则内容——匿名函数
匿名函数就是不需要显式的指定函数
1 #这段代码 2 def calc(n): 3 return n**n 4 print(calc(10)) 5 6 #换成匿名函数 7 calc = lambda n:n**n 8 print(calc(10))
匿名函数主要是和其它函数搭配使用的呢,如下:
1 res = map(lambda x:x**2,[1,5,7,4,8]) 2 for i in res: 3 print(i) 4 5 # 输出 6 1 7 25 8 49 9 16 10 64
2、高阶函数:
- 把一个函数名当做一个实参传递给另外一个函数(在不修改被装饰函数的情况下为其添加功能)
- 返回值中包含函数名(不修改函数的调用方式)
1 import time 2 3 def bar(): 4 time.sleep(3) 5 print('in the bar') 6 7 def func_2(fun): 8 print(fun) 9 return fun 10 11 print(bar) 12 # 输出 13 <function bar at 0x100662e18> 14 15 bar = func_2(bar) 16 # 输出 17 <function bar at 0x100662e18> 18 19 bar() # run bar 20 in the bar #三秒后显示
3、嵌套函数
1 # 嵌套函数 2 def foo(): 3 print('in the foo') 4 def bar(): 5 print('in the bar') 6 bar() 7 8 foo() 9 # 输出 10 in the foo 11 in the bar
高阶函数+嵌套函数==>>装饰器
1 import time 2 3 def bar(): 4 time.sleep(3) 5 print('in the bar') 6 7 def func(fun): 8 start_time = time.time() 9 fun() # run bar 10 stop_time = time.time() 11 print('the running time of fun is %s' %(stop_time - start_time)) 12 13 func(bar) 14 # 输出 15 in the bar 16 the running time of fun is 3.0043821334838867
以上代码已经是一个简单的高阶函数+嵌套函数版的弱鸡装饰器了,但是:
写代码要遵循开发封闭原则,虽然在这个原则是用的面向对象开发,但是也适用于函数式编程,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已实现的功能代码块
- 开放:对扩展开发
so。。。
1 import time 2 3 def timer(func): 4 def warpper(): 5 start_time = time.time() 6 func() 7 stop_time = time.time() 8 print('the func run time is %s' %(stop_time-start_time)) 9 return warpper 10 11 12 @timer # time1 = timer(time1) 13 def time_1(): 14 time.sleep(3) 15 print('in the time_1') 16 17 time_1() 18 # 输出 19 in the time_1 20 the func run time is 3.0014379024505615 21 22 # 也可以传入参数 23 import time 24 25 def timer(func): 26 def deco(*args,**kwargs): 27 start_time = time.time() 28 func(*args,**kwargs) 29 stop_time = time.time() 30 print('the func run time is %s' % (stop_time - start_time)) 31 return deco 32 33 @timer # time2 = timer(time2) = deco time2()=deco() 34 def time2(*args,**kwargs): 35 print('time2:',args,kwargs) 36 37 time2('XMY',age=22,job='IT') 38 print(timer(time1)) 39 # 输出 40 time2: ('XMY',) {'age': 22, 'job': 'IT'} 41 the func run time is 9.608268737792969e-05 42 <function timer.<locals>.deco at 0x102693e18>
2、迭代器
迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退,不过这也没什么,因为人们很少在迭代途中往后退。另外,迭代器的一大优点是不要求事先准备好整个迭代过程中所有的元素。迭代器仅仅在迭代到某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合,比如几个G的文件
特点:
- 访问者不需要关心迭代器内部的结构,仅需通过next()方法不断去取下一个内容
- 不能随机访问集合中的某个值 ,只能从头到尾依次访问
- 访问到一半时不能往回退
- 便于循环比较大的数据集合,节省内存
1 # 可以直接作用于for循环的对象统称为可迭代对象: Iterable 2 3 from collections import Iterable 4 isinstance([], Iterable) 5 6 # 可以被next()函数调用并不断返回下一个值的对象称为迭代器: Iterator 7 # Iterator可以表示一个无限大的数据流 8 from collections import Iterator 9 isinstance([], Iterator) 10 isinstance(iter([]), Iterator) 11 12 print(globals())
3、生成器
定义:一个函数调用时返回一个迭代器,那这个函数就叫做生成器(generator),如果函数中包含yield语法,那这个函数就会变成生成器 .
yield的主要效果是可以使函数中断,并保存中断状态,中断后,代码可以继续往下执行,过一段时间还可以再重新调用这个函数,从上次yield的下一句开始执行。
1 # 列表生成式 2 l = [i ** 2 for i in range(10)] 3 print(l) 4 # 输出 5 [0, 1, 4, 9, 16, 25, 36, 49, 64, 81] 6 7 ''' 8 相当于 9 a = [] 10 for i in range(10): 11 a.append(i**2) 12 13 print(a) 14 ''' 15 16 # 生成器 17 # 只有在调用时才回生产相应的数据 18 # 只记录当前位置 19 # 只有一个__next__()方法 20 g = (i ** 2 for i in range(10)) 21 print(g) 22 # 输出 23 <generator object <genexpr> at 0x102155620> 24 25 for i in g: # g.__next__() 26 print(i) 27 # 输出 28 0 29 1 30 4 31 9 32 16 33 25 34 36 35 49 36 64 37 81 38 39 # Fibonacci 40 def fib(max): 41 n, a, b = 0, 0, 1 42 while n < max: 43 # print(b) 44 yield b 45 a, b = b, a + b 46 ''' 47 相当于 48 t = (b, a + b) 49 a = t[0] 50 b = t[1] 51 ''' 52 n = n + 1 53 return 'done' 54 55 56 f = fib(10) 57 while True: 58 try: 59 x = next(f) 60 print('f:',x) 61 except StopIteration as e: 当生成器取完时中断、避免报错 62 print('Generator return value:', e.value) 63 break 64 # 输出 65 f: 1 66 f: 1 67 f: 2 68 f: 3 69 f: 5 70 f: 8 71 f: 13 72 f: 21 73 f: 34 74 f: 55 75 Generator return value: done
另外,还可通过yield实现在单线程的情况下实现并发运算的效果
1 # 协程 2 import time 3 def consumer(name): 4 print('%s 准备吃包子了' %name) 5 while True: 6 baozi = yield 7 print('包子[%s]来了,被[%s]吃了' %(baozi,name)) 8 9 def producer(name): 10 c1 = consumer('A') 11 c2 = consumer('B') 12 c1.__next__() 13 c2.__next__() 14 print('老子要开始做包子了!') 15 for i in range(10): 16 time.sleep(1) 17 print('做了2个包子!') 18 c1.send(i) 19 c2.send(i) 20 21 producer('Xmy')
4、序列化&反序列化
如果我们要存储和读取一个列表或字典类型的文件,根据之前所学的知识,只能通过如下方法:
1 info = { 2 'name':'xmy', 3 'age':24, 4 } 5 6 7 f = open('test.txt','w') 8 f.write(str(info)) # 以字符串格式写入文件 9 f.close() 10 11 f = open('test.txt','r') 12 data = eval(f.read()) # 读入文件,并转为对应dict格式 13 f.close
除了这种比较麻烦的蠢办法以外,我们还可以用json和pickle两种方法,将对文件进行读取操作
1. json
1 import json 2 3 info = { 4 'name':'xmy', 5 'age':24, 6 } 7 8 f = open('test.txt','w') 9 json.dump(info,f) #写入文件,等同于f.write(json.dumps(info)) 10 f.close() 11 12 f = open('test.txt','rb') 13 data = json.load(f) #读取文件,等同于data = json.loads(f.read()) 14 f.close()
2. pickle
pickle的操作和json是一致的,唯一的区别在于,pickle是将原文件内容转为二进制写入文件中,在文件读取中也是从二进制转回。因此在文件操作时,需要标注'rb'、'wb'、'ab'。
此外,json无法对函数进行文件存储,而pickle可以.
1 import pickle 2 3 def sayhi(name): 4 print('hello',name) 5 6 info = { 7 'name':'xmy', 8 'age':24, 9 'func':sayhi, 10 } 11 12 f = open('test.txt','wb') 13 pickle.dump(info,f) # f.write(pickle.dumps(info)) 14 f.close()
但是,在读取的过程中,我们是否可以直接调用该函数呢?
1 import pickle 2 3 f = open('test.txt','rb') 4 5 data = pickle.load(f) # data = pickle.loads(f.read()) 6 7 f.close() 8 9 # 输出 10 AttributeError: Can't get attribute 'sayhi' on <module '__main__' #出现报错
可见,当初存入后,文件内只是存在了函数形式的字符串,当读取时,由于不存在该函数,所以会产生报错,但如果我们读取之前,再次定义一下sayhi函数,情况就不一样了。
1 import pickle 2 3 def sayhi(name): 4 print('hello world',name) 5 6 7 f = open('test.txt','rb') 8 9 data = pickle.load(f) # data = pickle.loads(f.read()) 10 11 f.close() 12 print(data['func']('Xmy')) 13 14 # 输出 15 hello world Xmy 16 None