什么是装饰器?
装饰器本质上就是一个python闭包函数
,他可以让其他函数在不需要做任何代码变动的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。
装饰器的应用场景:比如插入日志,性能测试,事务处理,缓存等等场景。
内部函数包含对外部作用域而非全剧作用域名字的引用,该内部函数称为闭包函数。常用的使用方法:嵌套函数
+ 内部函数调用外部函数的变量
。
举例1
def outer():
a = 1
def inner():
print(a)
inner()
outer()
举例2
def outer():
a = 1
def inner():
print(a)
return inner
inn = outer()
inn()
在例1
中如果去调用outer()
,命名空间里就会创建a = 1
,再创建inner()
,然后a
就会消失。每次调用outer()
的时候函数outer()
里的变量就会创建,结束就会消失,很是浪费资源。但是例2
中inner
函数当做内存地址传到外面,并且外部inn
会使用inner
,此时 a = 1
就会在内存中一直存在。
例3
def func():
name = 'liu'
def inner():
print(name)
return inner
f = func()
f()
例4
name = 'liu'
def func2():
def inner():
print(name)
print(inner.__closure__)
return inner
f2 = func2()
f2()
嵌套函数
:在例3
中有func()
,inner()
内部函数调用外部函数的变量
:在inner函数之外,func函数之内需要有变量被inner函数调用。如被例3
中的name变量。例4中name= 'liu'
是全局变量输出是None,不满足闭包函数的条件。
例5:闭包函数获取网络应用
from urllib.request import urlopen
def get_url():
url = 'https://www.baidu.com'
def get():
ret = urlopen(url).read()
print(ret)
return get
inn = get_url()
inn()
最简单的装饰器 : 1、有返回值的 2、有一个参数 3、万能参数
为什么要使用装饰器呢?下面我们看一下下面的需求:我们需要测试一下每个函数的执行时间。
在测试的函数量比较少的话,我们可以这样的写:
import time
def func1():
start = time.time()
print('in func1')
print(time.time() - start)
func1()
虽然上述的代码解决问题,但是如果我们成百上千的函数量,这样的方法就不太行了。这时你可能会想把计算函数执行的时间写一个函数来执行,如下面代码:
import time
def timer(func):
start = time.time()
func()
print(time.time() - start)
def func1():
print('in func1')
def func2():
print('in func2')
timer(func1)
timer(func2)
然而这样的工作量只是相对减少了一些,并不满意。那还记得上面讲的闭包函数吗,对的,这里我们要使用到闭包函数来解决做一下尝试看看效果如何。
import time
def func1():
print('in func1')
def timer(func):
def inner():
start = time.time()
func()
end = time.time()
print(end - start)
return inner
func1 = timer(func1)
func1()
效果还不错,简便了许多。或许这里你看的不太懂,里面来回的调用,有点被绕晕了。这里先不着急,慢慢往下看:
函数名其实就是内存地址。上图中func
就是闭包函数中说到的:内部函数调用外部函数的变量。python
是自上而下进行编译,所以执行顺序是执行两个函数:第一步:func1(),第二步:timer(),第三步:执行func的=右边timer函数把func1的内存地址作变量传入timer函数中,第四步执行inner函数,第五步把inner函数的内存地址返回,第六步func1接收到inner的值,第七步执行func1函数,第八步执行start,第九步执行函数func,调用func1函数,接下来依次往下执行。
虽然上述代码程序已经很完美了,但是每次都要去执行func1 = timer(func1)
?这样还是有点麻烦!因为这些函数的函数名可能是不相同,所以引出了语法糖的概念:@装饰器函数名
,具体代码用法如下:
import time
def timer(func):
def inner():
start = time.time()
func()
print(time.time() - start)
return inner
@timer #==> func1 = timer(func1)
def func1():
print('in func1')
func1()
装饰器的作用 —— 不想修改函数的调用方式 但是还想在原来的函数前后添加功能
timmer
就是一个装饰器函数,只是对一个函数 有一些装饰作用
func()
是一个被装饰的函数。
还有最后一个问题要解决,刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?
刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?
import time
def timer(func):
def inner(*args,**kwargs):
start = time.time()
re = func(*args,**kwargs)
print(time.time() - start)
return re
return inner
@timer #==> func1 = timer(func1)
def func1(a,b):
print('in func1')
@timer #==> func2 = timer(func2)
def func2(a):
print('in func2 and get a:%s'%(a))
return 'fun2 over'
func1('aaaaaa','bbbbbb')
print(func2('aaaaaa'))
函数参数的*args,**kwargs
几乎可以解决所有参数问题。上面的装饰器已经非常完美了,但是有我们正常情况下查看函数信息的方法在此处都会失效:
def index():
'''这是一个主页信息'''
print('from index')
print(index.__doc__) #查看函数注释的方法
print(index.__name__) #查看函数名的方法
为了不让他们失效,我们还要在装饰器上加上一点来完善它:
from functools import wraps
def deco(func):
@wraps(func) #加在最内层函数正上方
def wrapper(*args,**kwargs):
return func(*args,**kwargs)
return wrapper
@deco
def index():
'''哈哈哈哈'''
print('from index')
print(index.__doc__)
print(index.__name__)
根据上述多种代码的样例,我们可以得到下面这样的装饰器的基本固定格式。
def wrapper(f): #装饰器函数,f是被装饰的函数
def inner(*args,**kwargs):
'''在被装饰函数之前要做的事'''
ret = f(*args,**kwargs) #被装饰的函数
'''在被装饰函数之后要做的事'''
return ret
return inner
@wrapper #语法糖 @装饰器函数名
1.对扩展是开放的 为什么要对扩展开放呢? 我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。 2.对修改是封闭的 为什么要对修改封闭呢? 就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。 装饰器完美的遵循了这个开放封闭原则。
1.编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件), 要求登录成功一次,后续的函数都无需再输入用户名和密码
FLAG = False
def log(func):
def inner(*args,**kwargs):
global FLAG
if FLAG :
ret = func(*args,**kwargs)
return ret
else:
username = input('输入用户名:')
password = input('输入密码:')
if username == '刘东' and password == '666':
FLAG = True
ret = func(*args, **kwargs)
return ret
else:
print('登录失败')
return inner
@log
def shoplist_add():
print('增加一件商品')
def shoplist_del():
print('删除一件商品')
2.编写装饰器,为多个函数加上记录调用功能,要求每次调用函数都将被调用的函数名称写入文件
def log(func):
def inner(*args,**kwargs):
with open('log','a',encoding='utf-8') as f:
f.write(func.__name__+'\n')
ret = func(*args,**kwargs)
return ret
return inner
@log
def shoplist_add():
print('增加一件物品')
@log
def shoplist_del():
print('删除一件物品')
1.编写下载网页内容的函数,要求功能是:用户传入一个url,函数返回下载页面的结果 2.为题目1编写装饰器,实现缓存网页内容的功能: 具体:实现下载的页面存放于文件中,如果文件内有值(文件大小不为0),就优先从文件中读取网页内容,否则,就去下载,然后存到文件中
import os
from urllib.request import urlopen
def cache(func):
def inner(*args,**kwargs):
if os.path.getsize('web_cache'):
with open('web_cache','rb') as f:
return f.read()
ret = func(*args,**kwargs) #get()
with open('web_cache','wb') as f:
f.write(b'*********'+ret)
return ret
return inner
@cache
def get(url):
code = urlopen(url).read()
return code
# {'网址':"文件名"}
ret = get('http://www.baidu.com')
print(ret)