一、闭包的介绍
闭包可以保存函数内的变量
当闭包执行完毕,外部函数的变量才释放。
# 闭包的作用:可以保存外部函数的变量
# 闭包的形成条件
# 1.函数嵌套
# 2.内部函数使用了外部函数的变量或者参数
# 3.外部函数返回内部函数。这个使用了外部函数变量的内部函数称为闭包
# 1.函数嵌套
def func_out(): # 外部函数
num1 = 10
def func_inner(num2): # 内部函数
result = num1 + num2 # 2.内部函数使用外部函数的变量或者参数
print("结果为:{}".format(result))
return func_inner # 3.外部函数返回内部函数,不能写func_inner()
# 获取闭包对象
new_func = func_out() # 这个new_func就是闭包,这里的new_func=func_inner
new_func(1) # 执行闭包
new_func(10)
输出:
结果为:11
结果为:20
二、闭包的使用
# 外部函数接收姓名
def config_name(name):
# 内部函数保存外部函数的参数,并且完成数据显示的组成
def inner(msg):
print(name + "说:" + msg)
# 外部函数返回内部函数
return inner
# 创建闭包实例对象
tom = config_name("tom") # 闭包一旦创建完成,外部函数的变量或参数name就定了,以后就不用再传了
jerry = config_name("jerry")
# 执行闭包
tom("哥们,过来一下,我们一起玩耍")
# 执行tom()的时候就是执行inner,在执行inner的时候传了一个参数"哥们,过来一下,我们一起玩耍"给msg,在inner函数内部组织之前一直保存的name参数
jerry("明天可以吗?")
输出:
tom说:哥们,过来一下,我们一起玩耍
jerry说:明天可以吗?
这里,通过tom = config_name("tom") 和 jerry = config_name("jerry")
,实际上只用了一份代码就创建了两个实例,然后在调用tom()和jerry()的时候,print(name + "说:" + msg)
也只是用了这一份代码,提高了代码复用性。
三、修改闭包内使用的外部变量
简言之,闭包就是那个内部函数,修改闭包内使用的外部变量,就是在内部函数里修改外部变量。
在闭包内修改外部函数的变量,需使用nonlocal关键字。
def func_out():
num1 = 10
def func_inner(): # 1.函数嵌套
num1 = 20 # 在闭包内修改外部函数的变量
result = num1 + 10 # 2.内部函数使用外部函数变量
print(result)
return func_inner # 3.返回内部函数
new_func = func_out() # 创建一个闭包实例
new_func() # 调用闭包实例
输出:30
def func_out():
num1 = 10
def func_inner(): # 1.函数嵌套
num1 = 20 # 在闭包内修改外部函数的变量
result = num1 + 10 # 2.内部函数使用外部函数变量
print(result)
print("修改前的外部变量:", num1)
func_inner()
print("修改后的外部变量:", num1)
return func_inner # 3.返回内部函数
new_func = func_out() # 创建一个闭包实例
输出:
修改前的外部变量: 10
30
修改后的外部变量: 10
可见,在创建闭包的时候程序就已经运行了,并且在内部函数里不能直接修改外部函数的变量值
def func_out():
num1 = 10
def func_inner(): # 1.函数嵌套
global num1
num1 = 20 # 在闭包内修改外部函数的变量
result = num1 + 10 # 2.内部函数使用外部函数变量
print(result)
print("修改前的外部变量:", num1)
func_inner()
print("修改后的外部变量:", num1)
return func_inner # 3.返回内部函数
new_func = func_out() # 创建一个闭包实例
输出:
修改前的外部变量: 10
30
修改后的外部变量: 10
可见,global并不能修改外部函数的变量值(global是与全局变量关联的)
def func_out():
num1 = 10
def func_inner(): # 1.函数嵌套
nonlocal num1
num1 = 20 # 在闭包内修改外部函数的变量
result = num1 + 10 # 2.内部函数使用外部函数变量
print(result)
print("修改前的外部变量:", num1)
func_inner()
print("修改后的外部变量:", num1)
return func_inner # 3.返回内部函数
new_func = func_out() # 创建一个闭包实例
输出:
修改前的外部变量: 10
30
修改后的外部变量: 20
可见,nonlocal可以修改成功
小结:在闭包内修改外部函数的变量,需使用nonlocal关键字。
.
.
.
.
.
.
四、装饰器
装饰器是一个函数,嵌套函数,本质上是一个闭包函数。
原函数如下,
# 装饰器的目的:对已有函数进行额外的功能扩展,装饰器本质上是一个闭包函数,也是函数嵌套
# 装饰器的特点:
# 1.不修改已有函数的源代码
# 2.不修改已有函数的调用方式
# 3.给已有函数添加额外的功能
def comment():
print("发表评论")
comment()
需求:在“发表评论”前,验证是否已登录,装饰器写法:
# 装饰器的目的:对已有函数进行额外的功能扩展,装饰器本质上是一个闭包函数,也是函数嵌套
# 装饰器的特点:
# 1.不修改已有函数的源代码
# 2.不修改已有函数的调用方式
# 3.给已有函数添加额外的功能
def decorator(func): # 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数是装饰器
def inner():
print("已添加登录验证")
func() # 在内部函数里面对已有函数进行装饰,这里func()=comment()
return inner
def comment():
print("发表评论")
comment = decorator(comment) # 调用装饰器对已有函数进行装饰,comment=func,然后comment=inner
comment() # 实际是执行inner()
输出:
已添加登录验证
发表评论
# 装饰器的目的:对已有函数进行额外的功能扩展,装饰器本质上是一个闭包函数,也是函数嵌套
# 装饰器的特点:
# 1.不修改已有函数的源代码
# 2.不修改已有函数的调用方式
# 3.给已有函数添加额外的功能
def decorator(func): # 如果闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数是装饰器
print("装饰器执行了")
def inner():
print("已添加登录验证")
func() # 在内部函数里面对已有函数进行装饰,这里func()=comment()
return inner
# 装饰器语法糖写法:@装饰器名称。装饰器语法糖写法更简单
@decorator # @装饰器名称,此处实际是封装了comment = decorator(comment),以后comment=inner
def comment():
print("发表评论")
comment() # 实际是执行inner()
# 装饰器的执行时间:当当前模块加载完成,装饰器会立即执行对已有函数进行装饰
输出:
装饰器执行了
已添加登录验证
发表评论
1、@decorator等价于comment = decorator(comment)
2、装饰器的执行时间:当当前模块加载完成,装饰器会立即执行对已有函数进行装饰
3、命名上面文件名为mydecorator.py,当在别的文件中时
import mydecorator,模块加载完成,即立即执行装饰
mydecorator.comment(),这里实际是已装饰完成innter()函数
五、装饰器的使用
import time
def decorator(func):
def inner(): # 内部函数对已有函数进行装饰
begin = time.time() # 获取距离1970-1-1-0:0:1的时间差
func() # func() = 最早的work()
end = time.time()
dur = end - begin
print("函数执行完成耗时:", dur)
return inner
@decorator # work = decorator(work), work = inner
def work():
for i in range(10000):
print(i)
work() # 装饰完成,work = inner
六、通用的装饰器
通用的装饰器,这个装饰器可以装饰任意函数
规则:使用装饰器装饰已有函数的时候,内部函数的类型和被装饰函数的类型要保持一致:
被装饰的函数有参数,内部函数也要给参数;
被装饰的函数有n个参数,内部函数也要给n个参数;
被装饰的函数有返回值,内部函数也要有返回值
# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有参数的函数----------
def decorator(func):
def inner(a, b):
print("正在努力执行加法计算")
func(a, b)
return inner
#用装饰器语法糖方式装饰带参数的函数
@decorator # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(num1, num2):
result = num1 + num2
print("结果为:", result)
add_num(1, 2) # 即执行inner
输出:
正在努力执行加法计算
结果为: 3
# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有参数和有返回值的函数----------
def decorator(func):
def inner(a, b):
print("正在努力执行加法计算")
num = func(a, b)
return num
return inner
#用装饰器语法糖方式装饰带参数的函数
@decorator # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(num1, num2):
result = num1 + num2
return result
result = add_num(1, 2) # 即执行inner
print("结果为:", result)
输出:
正在努力执行加法计算
结果为: 3
通用的装饰器:
# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有不定长参数和有返回值的函数----------
def decorator(func):
def inner(*args, **kwargs):
print("正在努力执行加法计算")
# *args,把元祖里的每一个元素按位置参数传参
# **kwargs,把字典里的每一个键值对按关键字方式传参
# 这里对元祖和字典进行拆包,仅限于被装饰函数为不定长参数的函数使用
num = func(*args, **kwargs)
return num
return inner
#用装饰器语法糖方式装饰带参数的函数
@decorator # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(*args, **kwargs):
result = 0
# *args;元组类型;**kwargs:字典类型
for value in args:
result += value
for value in kwargs.values():
result += value
return result
result = add_num(1, 2) # 即执行inner
print("结果为:", result)
输出:
正在努力执行加法计算
结果为: 3
通用装饰器的使用:
# 通用的装饰器:可以装饰任意类型的函数
# -------装饰有不定长参数和有返回值的函数----------
def decorator(func):
def inner(*args, **kwargs):
print("正在努力执行加法计算")
# *args,把元祖里的每一个元素按位置参数传参
# **kwargs,把字典里的每一个键值对按关键字方式传参
# 这里对元祖和字典进行拆包,仅限于被装饰函数为不定长参数的函数使用
num = func(*args, **kwargs)
return num
return inner
#用装饰器语法糖方式装饰带参数的函数
@decorator # 等价于 add_num = decorator(add_num), add_num = inner
def add_num(*args, **kwargs):
return "哈哈"
result = add_num() # 即执行inner
print("结果为:", result)
输出:
正在努力执行加法计算
结果为: 哈哈
七、多个装饰器的使用
# 定义装饰器
def make_p(func):
def inner():
# 在内部函数对已有函数进行装饰
result = "<p>" + func() + "</p>" # func = content
return result
return inner
@make_p # content = make_p(content), content = inner
def content():
return "人生苦短,我用Python!"
result = content() # content = inner
print(result)
输出:
<p>人生苦短,我用Python!</p>
# 定义装饰器
def make_p(func):
print("make_p装饰器执行了")
def inner():
# 在内部函数对已有函数进行装饰
result = "<p>" + func() + "</p>" # func = content
return result
return inner
def make_div(func):
print("make_div装饰器执行了")
def inner():
# 在内部函数对已有函数进行装饰
result = "<div>" + func() + "</div>" # func = content
return result
return inner
@make_div
@make_p # content = make_p(content), content = inner
def content():
return "人生苦短,我用Python!"
result = content() # content = inner
print(result)
输出:
make_p装饰器执行了
make_div装饰器执行了
<div><p>人生苦短,我用Python!</p></div>
小结:
多个装饰器的过程:由内到外装饰,先执行内部的装饰器,再执行外部的装饰器
content = make_div(make_p(content))分步拆解:
1、content = make_p(content),内部装饰器装饰完成,content = make_p.inner
2、content = make_div(make_p.inner)
八、带参数的装饰器
def decorator(func): # 装饰器只能接收一个参数并且是函数类型
def inner(a, b):
print("正在执行加法计算")
func(a, b)
return inner
@decorator
def add_num(a, b):
result = a + b
print(result)
@decorator
def substract_num(a, b):
result = a - b
print(result)
add_num(1, 2)
substract_num(1, 3)
输出:
正在执行加法计算
3
正在执行加法计算 # 这句话是加法计算,不是我们想要的
-2
改进如下,
def return_decorator(flag): # return_decorator是一个普通函数
def decorator(func): # 装饰器只能接收一个参数并且是函数类型
def inner(a, b):
if flag == "+":
print("正在执行加法计算")
elif flag == "-":
print("正在执行减法计算")
func(a, b)
return inner
return decorator # 当调用函数的时候返回一个装饰器decorator
# return_decorator("+")函数返回decorator,→ @decorator => add_num = decorator(add_num)
@return_decorator("+")
def add_num(a, b):
result = a + b
print(result)
@return_decorator("-")
def substract_num(a, b):
result = a - b
print(result)
add_num(1, 2)
substract_num(1, 3)
输出:
正在执行加法计算
3
正在执行减法计算
-2
1、装饰器只能接收一个参数并且是函数类型
2、带参数的装饰器,其实就是在装饰器外面套了一个普通函数,让函数接收参数,在函数内部返回的是一个装饰器,再用装饰器去装饰函数
九、类装饰器
# 类装饰器,使用类装饰已有函数
class MyDecorator:
def __init__(self, func):
self.func = func # func = show
# 实现__call__()方法,让对象变成可调用对象,可调用的对象能够像函数使用
def __call__(self, *args, **kwargs):
print("课已讲完")
self.func()
@MyDecorator # @MyDecorator => show = MyDecorator(show)
def show():
print("快下课了")
show() # 执行show()→执行MyDecorator类创建类实例对象,show()=>对象()
print(show)
输出:
课已讲完
快下课了
<__main__.MyDecorator object at 0x000002BF218D8048>
拓展:函数能够被调用,是因为函数自带__call__方法
def test():
print("哈哈")
print(dir(test)) # dir(),返回函数内部的属性和方法,返回一个列表
输出:
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
十、装饰器方式添加路由
urlpatterns = [
path('admin/', admin.site.urls),
path("index/", views.index),
path("login/", views.myview.as_view())
]
示例,index函数已开发完毕,现在要在index的基础上增加新的功能,可以使用装饰器,因为需要同时告知router,因此是带参数的装饰器。
def route(path):
def decorator(func): # 装饰器要装饰函数,所以这里要有func形参并且是函数类型
# 当执行装饰器的时候就需要把路由添加到路由列表里,相当于此时已经urlpatterns.append(route, view.index)
urlpatterns = []
urlpatterns.append((path, func))
def inner(request):
print("这是装饰器方式添加的路由")
result = func(request)
return result
return inner
return decorator
@route("index/") # 等价于@decorator,接着index = decorator(index),接着index = inner
def index(request): # 视图函数负责处理业务逻辑,request对象封装所有请求信息
print(request)
return HttpResponse("我的首页呀,哈哈")
class myview(View):
def get(self, request):
return HttpResponse("这是基于类的视图实现方式")
PS: source, bilibili