1. 装饰器(decorator)的基础
1.1 基本概念
概念:
- 本质:函数
- 功能:装饰其他函数(为其他函数添加附加功能)
原则:
- 不能修改被装饰函数的源代码
- 不能修改被装饰函数的调用方式
- 不能修改被装饰函数的返回值
需要的知识储备:
- 函数即“变量”
- 高阶函数
- 嵌套函数
- 闭包
1.2 函数调用顺序:函数即"变量"
函数调用顺序注意两点:
- 函数没有声明之前,不能调用
- 声明的多个函数没有顺序之分
函数即“变量”:
- 变量名是一个盒子(数、字符串……)的标签,函数是一个“函数体”的钥匙(地址)。有了标签钥匙,就可以取内容。
#函数调用顺序 def bar(): print('in the bar') def foo(): print('in the foo') bar() foo() #上面定义顺序无所谓,python解释器一行行解释了,只要存在,就能用
三点注意:
- 理解钥匙
- 内存回收机制:钥匙被清空了,就不在了
- python解释性:python解释器一行行解释,找到各个钥匙对应的内容
1.3 高阶函数
定义:
- 把一个函数名当作实参传给另外一个函数 -->(在不修改被装饰函数条件下,为其添加功能)
- 返回值中包含函数名 -->(不修改函数的调用方式)
# 高阶函数示范(不修改被装饰函数) # 把钥匙inner交给函数outer def inner(): print ('in the bar') def outer(func): res = func() # func()等同于inner(),inner没有返回值,因此res=None print('收到的钥匙地址:%s' %func) # 打印的是inner钥匙(内存地址) func() # 钥匙():执行inner函数内容 return func(), res, func #理解这个地方很关键:钥匙房间返回值、钥匙地址 c = outer(inner) print(c) #(None, None, 0x000002293883C1E0)
#对@的理解:inner = outer(inner) 也是高阶函数的牛逼之处
#尝试1,不修改调用方式:失败(返回的钥匙还是收到的钥匙)
def inner(): print('in the inner') def outer(func): func() #inner内容 print('in the outer') #新加内容 return func #其实返回的钥匙还是收到的钥匙,只能后面用嵌套 outer(inner) print('------') inner = outer(inner) #形式对了,后面加入嵌套后就能改变钥匙
#尝试2,不修改调用方式:失败(返回的outer是有参数的) def inner(): print('in the inner') def outer(func): func() print('in the outer') print('inner的地址:%s' % inner) print('outer的地址:%s' % outer) return outer #修改返回的内容为outer inner = outer(inner) #功能多了,但调用方式还是不对,outer有参数 print('------') print(inner)
高阶函数的经典之处在于:传进去的是inner钥匙,传出来的是outer钥匙,再inner = outer(inner)一下,虽然名字还叫inner,但此时inner变为了outer房间的钥匙。
不过还有一个问题,outer开门需要参数,inner不需要,因此调用方式还是不一样。(新功能已经具备)
因此,我需要返回一个具有新功能,又没有参数的钥匙。因为要传原来的钥匙,因此想到函数的嵌套,2层函数,外层接受inner钥匙,内层定义一个与原函数调用方式相同的用来返回的函数。
1.4 嵌套函数
定义:在一个函数体内创建另外一个函数
#函数嵌套示范 x=0 def grandpa(): # x=1 def dad(): x=2 def son(): x=3 print (x) #son() dad() grandpa() #结果:无,因为x=3, print(x)未被执行
1.5 闭包
定义:
- 外函数中定义了一个内函数
- 内函数里运用了外函数的临时变量
- 外函数的返回值是内函数的引用(python中一切皆对象,有引用就好说,引用可理解为钥匙)
特点:
- 内函数里不能修改外函数的内容,除非把闭包变量修改成可变数据类型
- 外函数的临时变量不会随着外函数的结束而消失,而是保留给内函数
回顾:
- 函数名:inner---->函数名字,存了函数所在位置的引用
- 函数名():inner()---->函数名后紧跟一对括号,表示调用这个函数
#闭包 def outer(a): b = 10 def inner(): print(a+b) return inner demo1 = outer(5) #demo存的是outer的返回值,其实就是inner, demo2 = outer(7) #每次调用外函数,都返回不同的实例对向的引用,虽然功能一样,但不是同一个函数对向 print(demo1) print(demo2)
2. 装饰器(decorator)
装饰器:高阶函数+嵌套函数+闭包
2.1 无参装饰器
概念:
#无参装饰器:记录函数inner运行时间 import time #outer函数作为装饰器 def outer(para): def deco(): """修饰函数: 拿到钥匙, 开门, 且记录运行时间""" start_time = time.time() res = para() #保证被装饰函数原功能,以及记录返回值 stop_time = time.time() print("新功能--->运行时间为:%s" % (stop_time - start_time)) return res #保证被装饰函数返回值不变 return deco #@outer 等同于下面的inner = outer(inner) ,必须写在被装饰函数上部 #原函数(内) def inner(): time.sleep(2) print("我是原函数,不能动") #执行验证 inner = outer(inner) #一个问题,为什么嵌套之后就不输出了??怎么做到的?? print("------") inner()
2.2 有参装饰器
很简单,加入非固定参数定义即可
#有参装饰器 ''' 以下为装饰器 ''' import time def timer(func): def deco(*args, **kwargs): #非固定参数 start_time = time.time() res = func(*args, **kwargs) #非固定参数 stop_time = time.time() print("the func run time is %s" % (stop_time - start_time)) return res return deco ''' 原代码 ''' @timer #bar1 = timer(bar1) def bar1(): time.sleep(2) print('in the bar') @timer def bar2(name, age): time.sleep(3) print('bar2:', name, age) ''' 按原来方式运行 ''' bar1() bar2('alex', 22)
2.3 终极版:装饰器本身带参数
给下列程序添加功能,要求如下:
- 进入home通过本地文件认证
- 进入bbs通过远程ldap认证
- 仅用一个装饰器
有点难:实在理解不了没关系,2.2就已经能解决90%的问题了。
#源代码 def index():#首页 print("welcome to index page") def home(): print("welcome to home page") return "from home" def bbs(): print("welcome to bbs page") #运行 index() home() bbs()
首先搭建框架:
本质:装饰器带个参数,多加了一层,整体下移一层
#装饰器框架(不能运行) user, passwd = 'Zhu Yijun', 'abc123' def auth(auth_type): # authentication def outer_wrapper(func): def wrapper(*args, **kwargs): if auth_type == 'local': """本地文件验证""" res = func(*args, **kwargs) return res #成功才返回 elif auth_type == 'ldap': """远程ldap验证""" return wrapper return outer_wrapper @auth(auth_type='local') # 本地文件验证 @auth(auth_type='ldap') # 远程ldap验证
home()
整体代码如下:
#完整代码 #其实就是整体往下移了一层 import time user, passwd = 'zhuyijun', 'abc123' def auth(auth_type): #authentication def outer_wrapper(func): def wrapper(*args, **kwargs): if auth_type == 'local': """本地文件验证""" username = input("Username:").strip() password = input("Password:").strip() if user == username and passwd == password: print("\033[32;1mUser has passed authentication\033[0m") res = func(*args, **kwargs) print("------after authentication-----") return res # 保证返回值不变 else: exit("\033[31;1mInvalid username or password!\033[0m") elif auth_type == 'ldap': """远程ldap验证""" print("搞毛线ldap,不会。。。。") return wrapper return outer_wrapper #源代码 def index():#首页 print("welcome to index page") @auth(auth_type='local') #本地文件验证 #home = wrapper() def home(): print("welcome to home page") return "from home" @auth(auth_type='ldap') #远程ldap验证 def bbs(): print("welcome to bbs page") #运行 index() home() bbs()
3. 生成器
列表生成:
#列表生成式 #生成0-9的平方的列表 #平方函数 def square(a): sum = a**2 return sum a = [square(i) for i in range(10)] #列表生成式:使代码更简洁 print(a)
生成器(generator):不用像列表生成式那样先全部把数据准备好,而是一边循环一边计算(用到才存在,不用就不存在),这样可以节约大量的内存。尤其是上百万元素时候,可以节省很多时间
#简单生成器 a = (i**2 for i in range(10)) #[]变成()即可
特点:
- 你调用它,它才生成;你不访问它,它根本不存在。换而言之,仅仅给你准备好了算法而已
- 不能用切片去取,只能循环先生成
取值:next()方法
- 可以通过next()函数获得generator的下一个返回值,只能一步一步走
- 不知道前面,也不知道后面,只保留一个值,当前值
#最常用的取值方法是for循环,而不是next g = (x**2 for x in range(10)) for n in g: print(n)
#斐波拉契数列函数
#假如需要执行10分钟
def fib(max): n, a, b = 0, 0, 1 while n < max: print(b) a, b = b, a + b #a, b = c, d:表示a=c,b=d,好处,c和d都是变化之前的 #不是下面格式 #a = b #b = a + b #a已经变了 n = n + 1 return 'done' fib(100)
#函数变成生成器 def fib(max): n, a, b = 0, 0, 1 while n < max: yield b #这个函数就不再是一个普通函数,而是一个generator a, b = b, a + b n = n + 1 #return 'done' #有没有无所谓 f = fib(10) print(f.__next__()) print("可以干点别的") print(f.__next__()) print("不用等待函数结束") print(f.__next__())
yield b:
- 返回当前值
- 停在这,保存函数中断状态,想回来就回来
什么时候停:用到捕获错误
- 直接复制:print(f.__next__()),超过会报错
- 用for循环:虽然会停,但没有返回值
- 因此用while+捕获错误
#想要拿到返回值,必须捕获StopIteration错误 f = fib(5) while True: try: x = next(f) print('f:', x) except StopIteration as e: #一但出了"StopIteration"错误,就执行下面语句 print('Generator return value:', e.value) #上面return的作用在这 break
send和next区别:
- c.send(b1):唤醒且传值,,唤醒就是指调用
- c.__next__():仅唤醒
吃包子程序:
- 不同角色之间来回切换,感觉就是并行的
- 看起来是单线程下的并行效果,其实是协程
#包子程序 import time #消费者 def consumer(name): print("%s 准备吃包子啦!" %name) while True: baozi = yield #保存当前状态,返回 print("包子[%s]来了,被[%s]吃了!" %(baozi,name)) ''' #流程试验: c = consumer("ZhuYijun") #仅表示c只是一个生成器,已经不是函数了,不会打印:print("%s 准备吃包子啦!" %name) c.__next__() #next一下才会从头往下走, b1 = "韭菜馅" c.send(b1) #send,把包子传进去了,被yield接收到了,赋给了包子 #c.__next__() #单纯调用,不会传值 ''' #生产者 def producer(name): #先生成2个消费者 c = consumer('A') c2 = consumer('B') #初始化,准备吃包子 c.__next__() c2.__next__() print("我开始准备做包子啦!") #循环10次,每次生成1个包子 for i in range(10): time.sleep(1) print("做了1个包子,分两半!") c.send(i) c2.send(i) producer("alex") #2个消费者,相当于3个任务同时运行
4. 迭代器
可迭代对象(Iterable):可直接for循环的对象:generator、set、tuple、dict……
- 注:可用isinstance()判断是否是“可迭代对象 ”
生成器不但可以作用于for
循环,还可以被next()
函数不断调用并返回下一个值,直到最后抛出StopIteration
错误表示无法继续返回下一个值了。
迭代器(Iterator):可以被next()
函数调用并不断返回下一个值的对象
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数实现按需计算下一个数据,所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。因此list、dict、str等数据类型默认不是Iterator。
总结:
- 凡是可作用于
for
循环的对象都是Iterable
类型; - 凡是可作用于
next()
函数的对象都是Iterator
类型,它们表示一个惰性计算的序列; - 集合数据类型如
list
、dict
、str
等是Iterable
但不是Iterator
,不过可以通过iter()
函数获得一个Iterator
对象。 - Python的
for
循环本质上就是通过不断调用next()
函数实现的,封装了而已 - 迭代器其实就是生成器,换一个称呼,比较底层,但要理解概念