闭包的定义与实现
1)定义
闭包就是指有权访问另一个函数作用于中的变量的函数,具有提高代码可复用性的作用。
2)实现闭包的三个条件
闭包的另一个常用场景就是装饰器
- 函数里面嵌套函数
- 内部函数使用了外部函数的临时变量
- 外部函数的返回值是内部函数的引用
验证闭包的实现条件
1)代码
# 定义函数不执行函数里面的代码,调用函数和时才执行函数里面的代码
# 所以次代码返回值时wrapper
def timeit(name):
# wapper 包装纸
def wapper():
print(name) # 内部函数使用外部函数的变量
return 'ok'
return wapper # 外部函数的返回值是内部函数的引用(函数名)
result = timeit("lyqiu")
print(result)
2)结果展示
调用timeit函数,函数的返回值是wrapper函数引用,上面代码中的result就是wrapper函数
调用函数
1)代码
# 方法1
result1 = result() # result的结果是wapper的函数名,此处等同于result1 = wapper()进行函数的调用
print(result1)
# 方法2
print(result()) #直接在result = timeit('lyqiu') 后打印输出result()
2)结果展示
装饰器的定义
- 器指的是工具,而程序中的函数就是具备某一功能的工具,所以装饰器指的是为被装饰器对象添加额外功能的工具/函数,装饰器就是用来装饰函数或类的工具
- 装饰器其实就是在遵循以下两个原则(开放封闭原则)的前提下为被装饰对象添加新功能
开放 | 封闭 |
---|---|
对扩展开放 | 对已经实现的功能代码块封闭,不修改被装饰对象的源代码 |
装饰器的实现
- 装饰器本质上是一个函数,该函数是用来处理其他函数。
- 它可以让其他函数在不需要求改代码的前提下增加额外的功能
- 并且装饰器的返回值也是一个函数对象
- 装饰器装饰的是函数/类,所以需要传递的参数是函数名或者类名
- 使用时: @装饰器名字
装饰器的应用(一)
分为创建装饰器和使用装饰器这两部分
通过下载音乐的练习,使我们对装饰器的使用有一个初步了解
1)实验前提
pip3 install requests -i https://pypi.douban.com/simple
安装
- 为计算下载时间可以使用时间戳
2)代码
import time #时间处理模块
import requests #HTTP请求库,多用于网络爬虫,需要pip install下载
# 1.如何去创建装饰器
#需求:添加功能-计算被装饰函数运行的时间的工具
def timeit(f):
def wrapper(): #添加被装饰函数执行的时间
start = time.time() #函数执行之前计算当前的时间戳
result = f() #调用被装饰的函数,并保存函数的返回值
end = time.time() #函数执行后计算当前的时间戳
print("函数%s执行使用的时间是%.3fs"%(f.__name__,end-start))
return result
return wrapper
# 2.如何去使用装饰器
@timeit
def download_music():
url="http://m10.music.126.net/20200719111612/e36c0e235dbad219e9d8f0e65fa62007/ymusic/0201/7233/bea2/2cb43c8bcaa7797d32e5ca9b831350d8.mp3"
response = requests.get(url) #模拟浏览器访问mp3的网址,获取服务器端给我们的响应(response)
#music_content = response.content #获取mp3音乐的内容
with open("再见.mp3", "wb") as f: #打开文件,存储音乐内容到文件中
#f.write(music_content)
f.write(response.content)
print("再见.mp3下载完成......")
download_music()
3)解释装饰器的使用原理
- f.write(response.content)可参考文件的读写操作
- @timeit工作原理:download_music=timeit(download_music),timeit为装饰器,将函数名download_music作为参数传过去,最终将返回值赋值给函数downliad_music
执行的过程:
- timeit(download_music)函数的返回值时wraper函数名。
- download_music=timeit(download_music),让download_music指向wrapper函数
- 最后一行downliad_music(),实质上是执行的函数wrapper()
- 执行wrapper函数时,f()实质上执行的函数download_music()
4)结果展示
装饰器的应用(二)
1)代码
import time #时间处理模块
def timeit(f):
def wrapper(*args, **kwargs):
"""
# *wargs和**kwargs是形参 (定义函数是形参)
# *args:可变参数,可以接受多个参数信息,一般存储到元祖中
# **kwargs:关键字参数,可以接受多个键值对信息,一般存储到字典中
# wrapper(10, 20)接收时用元组来存储(10, 20)
"""
start = time.time()
result = f(*args, **kwargs)
"""
# 调用被装饰的函数,并保存函数的返回值
# *args和**kwargs是实参 (调用函数是实参)
# f(10, 20)==add(10, 20) 要传的是10,20而不是元祖(10,20),所以在上面使用解包
# 注意:这里*args不是可变参数的意思,是解包的意思,args=(10, 20)是一个元祖;*args=10,20 即把一
个元祖变成两个数值;**kwargs是将一个字典解包成键值对
"""
end = time.time()
print("函数%s执行使用的时间是%.3fs"%(f.__name__,end-start))
return result
return wrapper
#2.如何去使用装饰器
@timeit
def add(num1, num2):
time.sleep(0.2) #休眠0.2s
return num1 + num2
"""
分析add函数的执行过程:
1.add(10, 20)调用函数
2.发现add函数被装饰器timeit装饰了,遇到 @timeit想到其执行原理是 timeit是装饰器,将函数名add作为参数传回去,最后将返回值赋值给函数add 即:add=timeit(add)
3.timeit(add)函数的返回值是wrapper函数,add=timeit(add)这里add=wrapper
4.终于知道add函数是什么了,add(20, 20)==>wrapper(10, 20)
5.在wrapper函数中有f()==>add(10, 20)
6.返回f()函数的返回值30
"""
print (add(10, 20))
2)分析执行过程
分析add函数的执行过程:
1.add(10, 20)调用函数
2.发现add函数被装饰器timeit装饰了,遇到 @timeit想到其执行原理是 timeit是装饰器,将函数名add作为参数传回去,最后将返回值赋值给函数add 即:add=timeit(add)
3.timeit(add)函数的返回值是wrapper函数,add=timeit(add)这里add=wrapper
4.终于知道add函数是什么了,add(20, 20)==>wrapper(10, 20)
5.在wrapper函数中有f()==>add(10, 20)
6.返回f()函数的返回值30
3)结果展示
编写装饰器的模板
1)模板
以打印日志信息为例
#编写装饰器模板
# 1.先实现闭包
# 2.要让这个装饰器可以装饰所有函数/类(代表汉和函数可以接受有参数/无参数)
import time
def logger(f):
def wrapper(*args, **kwargs): #这里是可变参数和关键字参数
start = time.time()
result = f(*args, **kwargs) #这里是解包
end = time.time()
print("Logger: %s %s run %.3f s" %(time.ctime(), f.__name__, end-start)) 获取当前时间 + f是个变量名,获取其真实名字使用双下划线
return result
return wrapper
@logger
def add(num1, num2):
time.sleep(0.1) # 为了实验结果更加明显,设置休眠0.1s
return num1 + num2
result = add(10, 20)
print(result)
2)结果展示
保留被装饰的函数原有的帮助信息
- 在上述模板中最后输入
print(help(add))
,查看add函数的帮助信息。此时出现的是wrapper函数的帮助信息。所以我们需要研究如何让被装饰的函数保持自己原有的帮助信息
1)代码
#编写装饰器模板
# 1.先实现闭包
# 2.要让这个装饰器可以装饰所有函数/类(代表汉和函数可以接受有参数/无参数)
# 3.如何让被装饰的函数报出自己原有的帮助信息呢?
import time
from functools import wraps
def logger(f):
@wraps(f) #wraps装饰器用来保留f函数原有的属性,包括他的帮助信息
def wrapper(*args, **kwargs): #这里是可变参数和关键字参数
"""
wrapper function
"""
start = time.time()
result = f(*args, **kwargs) #这里是解包
end = time.time()
print("Logger: %s %s run %.3f s" %(time.ctime(), f.__name__, end-start))
return result
return wrapper
@logger #add = loggger(add)==> add=wrapper
def add(num1, num2):
"""
add function
"""
time.sleep(0.1)
return num1 + num2
#result = add(10, 20)
#print(result)
print(help(add))
2)结果展示
多个装饰器
多个装饰器装饰的时候,从下向上装饰,执行的时候是从上到下进行执行。
1)代码
此时一个是欢迎信息,另一个是打印日志信息
#编写装饰器模板
# 1.先实现闭包
# 2.要让这个装饰器可以装饰所有函数/类(代表汉和函数可以接受有参数/无参数)
# 3.如何让被装饰的函数报出自己原有的帮助信息呢?
import time
from functools import wraps
def welcome(f):
@wraps(f) # 保持原有函数的帮助信息
def wrapper(*args, **kwargs):
print("welcome......")
result = f(*args, **kwargs)
return result
return wrapper
def logger(f):
#这里是可变参数和关键字参数
#wraps装饰器用来保留f函数原有的属性,包括他的帮助信息
@wraps(f)
def wrapper(*args, **kwargs):
"""
wrapper function
"""
start = time.time()
#这里是解包
result = f(*args, **kwargs)
end = time.time()
print("Logger: %s %s run %.3f s" %(time.ctime(), f.__name__, end-start))
return result
return wrapper
# 多个装饰器装饰的时候,从下向上装饰,执行的时候是从上到下进行执行。
@welcome
@logger #add = loggger(add)==> add=wrapper
def add(num1, num2):
"""
add function
"""
time.sleep(0.1)
return num1 + num2
result = add(10, 20)
print(result)
2)结果展示