Python之装饰器
在不修改函数调用方式的前提下,也不能修改函数内部源代码!!!!
例如:
在每个季度公司发绩效,统计每个人的代码执行效率。咱们总不能是每个函数里加time模块吧。
import timedef test():start = time.time() #开始时间time.sleep(0.1) #睡一下,象征的表示函数的代码块很大,要0.1秒去执行print("hello") #打印函数结果end = time.time() #函数结束时间print(end-start) #计算函数的执行时间,并打印 test()
但是,在公司,咱么不可能使每人都这么去执行代码,加time.time()。
原因很简单,就是这样已经更改了原函数的的内部代码。
并且,这个计算时间的功能只是在季度末用来算代码执行时间的,过阵子就不用了。咱么总不能在去每个函数里把time给注释或删了吧。要是那样,呵呵呵,真麻烦。
那问题来了。我们怎么去解决这个问题。
哈哈哈:在写一个函数呗,函数功能只是单纯的计算时间的。
import timedef timmer(func):#函数名可以当做函数的参数start = time.time()func()end = time.time()print(end-start)def test():time.sleep(0.1)print("hello")timmer(test) #函数test作为参数传给timmer函数。
But ,问题又来了,这个样就改变了函数的调用方式。也是不可以的。
这样的话,就有了今天的换题。装饰器。
我们讲test = timmer 就是说,讲timmer的函数地址给test
在调用test函数 test() 实际就是在调用timmer函数
在这个时候,执行test等同于执行timmer,可是timmer里要有参数的。而参数是test,可以咱们又将test的地址指向了timmer。这样的话。在内存中就没有了test函数啦~~~~~~。咋办哩?
这里我们就用到了之前不知道干啥用的闭包了。
在timmer函数中,写一个闭包函数。
1 import time2 def timmer(func):# 函数名可以当做函数的参数5 def inner(x,y):9 start = time.time()10 func(x,y) # 传入的test函数11 end = time.time()12 print(end-start)6 return inner # inner的地址给了test3 def test(a,b):time.sleep(0.1)print(a,b)7 h = 4 timmer(test) #test为inner #函数test作为参数传给timmer函数。
8 h(1,2) # 相当于执行inner
这样是闭包的内部函数去调用外部函数的值,使外部函数的值不消失。
在这里,以调用timmer,就返回一个inner。而这个inner就是现在的h。h传有参数1和2,就是传给inner函数1和2。1和2就是inner的x和y,但是inner的x和y是test函数需要的a和b。而test函数就是inner函数里的func(x,y)。
这就是一个带参数的装饰器。
但是在多个函数使用这个装饰器的时候,会因为参数的不固定。报错。
那么,怎么解决呐?
import time def timmer(func):def inner(x,y):func(x,y)return innerdef test1(a,b):time.sleep(0.1)print(a,b)def test2(a):print(a)test1 = timmer(test1) test1(1,2)test2 = timmer(test2) test2(1)
报错信息:少位置参数。在inner函数里是两个参数。
1 2 Traceback (most recent call last):File "/Users/george/PycharmProjects/Python/Python函数/装饰器/test.py", line 85, in <module>test2(1) TypeError: inner() missing 1 required positional argument: 'y'
解决方法:
使用 *args 和 **kwargs 动态传参。
import time def timmer(func):def inner(*args,**kwargs): func(*args,**kwargs)return innerdef test1(a,b):time.sleep(0.1)print(a,b)def test2(a):print(a)test1 = timmer(test1) test1(1,2)test2 = timmer(test2) test2(1)
@timmer == test1 = timmer(test1)
将@timmer加在函数的上一行。
import time def timmer(func):def inner(*args,**kwargs):"""ret是返回值:param args::param kwargs::return:"""ret = func(*args,**kwargs)return retreturn innerdef test1(a,b):time.sleep(0.1)print(a,b)def test2(a):print(a)@timmer #test3 = timmer(test3) def test3():"""带返回值的函数,装饰器里的inner函数也要有返回才行。:return:"""return 123test1 = timmer(test1) test1(1,2)test2 = timmer(test2) test2(1)ret = test3()
装饰器的本质:闭包函数
功能:就是在不改变原函数调用方式的情况下,在这个函数前后加扩展功能
装饰器的使用:
wrapper(装饰)
def wrapper(func):def inner(*args,**kwargs):"""添加函数调用之前的扩展代码"""ret = func(*args,**kwargs)"""添加函数调用之后的扩展代码"""return retreturn inner####设计模式:# 原则:开放封闭原则# 对扩展是开放的# 对修改是封闭的
装饰器示范:
import timedef wapper(func):"""计算代码的运行时间。:param args::param kwargs::return: 时间差"""def inner():print(func) # 打印的func是index的内存地址。#func函数被扩展前的调用代码start_time = time.time()# 被修饰的函数 func()#func函数别扩展后的代码end_time = time.time()print("run time is %s " %(end_time-start_time))return inner@wapper # @wapper 实际上是 index = wapper(index) def index():time.sleep(0.3)print(".......")index() #index() 实际上是在执行inner
开放封闭原则:
1.对扩展是开放的
为什么要对扩展开放呢?
我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。
2.对修改是封闭的
为什么要对修改封闭呢?
就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。
装饰器完美的遵循了这个开放封闭原则。
装饰器的主要功能和装饰器的固定结构
装饰器的主要功能:
在不改变函数调用方式的基础上在函数的前、后添加功能。
装饰器的固定格式:


1 def timer(func): 2 def inner(*args,**kwargs): 3 '''执行函数之前要做的''' 4 re = func(*args,**kwargs) 5 '''执行函数之后要做的''' 6 return re 7 return inner


1 from functools import wraps 2 3 def deco(func): 4 @wraps(func) #加在最内层函数正上方 5 def wrapper(*args,**kwargs): 6 return func(*args,**kwargs) 7 return wrapper
无参装饰器:示范
@wapper 后面么有跟参数。
import timedef wapper(func):"""计算代码的运行时间。:param args::param kwargs::return: 时间差"""def inner(*args,**kwargs):print(func) # 打印的func是index的内存地址。#func函数被扩展前的调用代码start_time = time.time()# 被修饰的函数res = func(*args,**kwargs)#func函数别扩展后的代码end_time = time.time()print("run time is %s " %(end_time-start_time))return resreturn inner@wapper # @wapper 实际上是 index = wapper(index) def index(*args,**kwargs):time.sleep(0.3)print(".......")@wapper def home(name):time.sleep(0.2)print("home is %s " %(name))@wapper def auth(name,password):"""认证:return:"""print(name,password)@wapper def my_max(x,y):print("my_max function")res = x if x>y else yreturn resres = my_max(1,22) # res = my_max(1,22) 就是在res = warpper(1,22) print("===",res)
auth("wang","111") home("HZ") index() #index() 实际上是在执行inner
有参装饰器:示范
@wapper 后面有参数的。
将已有的装饰器再被嵌套一层函数进去,
def test1():
def wapper():
这样,test1就可是一个带参数的装饰器了。
def wapper(auth_type):"""参数装饰器了。:param auth_type::return:"""def wapper2(func):"""认证功能装饰器:param func::return:"""print(auth_type)def inner(*args,**kwargs):if auth_type == "file":print("func the function", func)name = input("username >>:")password = input("password >>:")if name == "george" and password == "111":print("ok successfull")res = func(*args,**kwargs)return reselse:print("error NO")elif auth_type == "SQL":print("MySQL,NB大了")return innerreturn wapper2@wapper(auth_type = "SQl") #参数了。。。。。。。 将原有的wapper在嵌套进一层,把auth_type搞成参数 def index():print("有参数了。不要脸了")index()
作业:
一:编写装饰器,为多个函数加上认证功能(用户的账号密码来源于文件)。
要求登录成功一次,后续的函数则无需再输入用户名和密码。
注意:从文件中读出字符串的字典,可以用eval({ ‘name’:‘George’,‘password’:‘123’})转成字典格式。
将字典写到一个文件中。名为conf.txt
在文件中读取的内容是字符串类型。
使用eval方法将类型进行转换:
最终结果:
#定义一个全局字典,告诉我user是谁,status状态是什么。 auth_status = {'user':None,'status':False }def auth_wrapper(func):"""认证装饰器:param func::return:"""def inner(*args,**kwargs):"""如果status的状态是真,则无需输入密码。:param args::param kwargs::return:"""#判断登录状态if auth_status['status']:ret = func(*args, **kwargs)return retelse:# 认证功能:# 获取用户名和密码username = input('username:').strip()password = input('password:').strip()# 读文件获取用户信息f = open('conf')user_info = f.read()user_dict = eval(user_info)# 对比输入的用户名和密码和文件里的是否一致。# user_dict.get能get到username,说明有这个用户存在,然后再去user_dict的字典里去取用户名k,得到密码的值,再和输入的密码对比。if user_dict.get(username) and user_dict[username] == password:print('login successful')#登录成功,将auth_status的值进行修改auth_status['user'] = usernameauth_status['status'] = Trueret = func(*args, **kwargs)return retelse:print('login feiled')return inner@auth_wrapper def index():"""首页函数:return:"""print('Welcome the world')@auth_wrapper def home():"""主页函数:return:"""print('Welcome the home')index() home()
二:编写下载页面内容的函数,要求功能是:用户传入一个URL,函数返回下载页面的结果。
编写装饰器,实现缓存页面内容的功能:
具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),
就优先从文件中读取页面内容,否则,就去下载,然后存到文件中。
先实现需求一,获取url。使用urllib模块
代码:
#先导入urllib模块。 from urllib.request import urlopendef get(url):"""给一个URL,返回结果:param url::return:"""return urlopen(url).read()url = get('http://www.cnblogs.com/george92/p/8629064.html') print(url)
加装饰器实现缓存:
def get_cache(func):"""缓存装饰器:param func::return:"""def inner(*args,**kwargs):#取func中的URLurl = args[0]# 用hash转换url,在转换成字符串类型filename = str(hash(url))#判断url是否在url_l中。if url in url_l:f = open(filename,'rb')ret = f.read()else:#不在url_l中,就将url添加到url_l中。 url_l.append(url)ret = func(*args,**kwargs)#打开文件f = open(filename,'wb')#将ret写到文件中 f.write(ret)#关闭文件 f.close()return retreturn inner
最终效果:
url_l = []#先导入urllib模块。 from urllib.request import urlopendef get_cache(func):"""缓存装饰器:param func::return:"""def inner(*args,**kwargs):#取func中的URLurl = args[0]# 用hash转换url,在转换成字符串类型filename = str(hash(url))#判断url是否在url_l中。if url in url_l:f = open(filename,'rb')ret = f.read()else:#不在url_l中,就将url添加到url_l中。 url_l.append(url)ret = func(*args,**kwargs)#打开文件f = open(filename,'wb')#将ret写到文件中 f.write(ret)#关闭文件 f.close()return retreturn inner@get_cache def get(url):"""给一个URL,返回结果:param url::return:"""return urlopen(url).read()url = get('http://www.cnblogs.com/george92/p/8629064.html') print(url) print(url) print(url) print(url) print(url) print(url) print(url) print(url) print(url)
带参数的装饰器:
给装饰器加一个开关,想用的时候就用,不想的时候就不用。 好TM任(ren)性。
# F = True F = False# 给wrapper装饰器见一个外层函数outer,在判断outer的参数是什么。在分别执行不同的代码块 def outer(flag):def wrapper(func):def inner(*args,**kwargs):#当被装饰的函数的值为flag时。if flag:print("before")ret = func(*args,**kwargs)print("after")#当被装饰的函数的值不为flag时:else:ret = func(*args,**kwargs)return retreturn innerreturn wrapper# @outer(True) ===> @wrapper ===> test = wrapper(test) ===> test == inner 所以在调test时就等同于调inner @outer(F) def test():print('呵呵呵')@outer(F) def test2():print('哈哈哈')test() test2() test()
多个装饰器修饰一个函数:
#应用场景
#func
#1.计算func的执行时间 @timmer
#1.登录认证 #@auth
#@auth
#@timmer
#func
#解耦 尽量的让代码分离。小功能之间的分离
#解耦的目的 提高代码的重用性
def test1(func):def inner(*args,**kwargs):print('test1:before')ret = func(*args,**kwargs)print('test1:after')return retreturn innerdef test2(func):def inner(*args,**kwargs):print('test2:before')ret = func(*args,**kwargs)print('test2:after')return retreturn inner@test1 @test2 def test():print('幺妹')
结果是个俄罗斯套娃
流程:
在调用test时:
是test = test2(test)
再test = test1(test),但test = test1(test) ==》test = test1(test2(test))
而test = test1(test2(test))的结果是 test2(test) ==> test2_inner
test1(test2_inner) = test1_inner
所以 test() ==> test1_inner()
小功能:
实现统计当前程序中有多少个函数被此装饰器装饰。
统计当前程序中有多少个函数被装饰了
l = []def wrapper(func):l.append(func)def inner(*args,**kwargs):ret = func(*args,**kwargs)print('wrapper:after')return retreturn inner@wrapper def test1():print('test1')# @wrapper def test2():print('test2')@wrapper def test3():print('tset3')@wrapper def test4():print('test4')test1() test2() test3() test4() print(l) print(len(l))
实现统计本次程序执行有多少个带这个装饰器的函数被调用。
l = [] def wrapper(func): def inner(*args,**kwargs):l.append(func)ret = func(*args,**kwargs)print('wrapper:after')return retreturn inner@wrapper def test1():print('test1')# @wrapper def test2():print('test2')@wrapper def test3():print('tset3')@wrapper def test4():print('test4')test1() test2() test3() test4() print(l) print(len(l))
总结;
装饰器
def wrapper(func):
def inner(*args,**kwargs):
ret = func(*args,**kwargs)
return ret
return inner
返回值
def ret_func():
print(111)
return 222
ret = ret_func()
print(ret)
def func1():
def func2():
return 123
r=func2()
# r = func2()
return r
func1()
print(r)
def wrapper(func):
def inner(*args,**kwargs):#定义函数的时候——*参数的聚合
ret = func(*args,**kwargs) #调用函数的时候——*参数的打散
#func是被装饰的函数,ret是被装饰函数的返回值
return ret #把被装饰的函数的返回值返回给调用者
return inner
@wrapper #hahaha = wrapper(hahaha)
def test(a,b):
return a+1,b+1
ret = test(1,2)
print(ret)