我们前面已经学过了函数,我们知道当函数调用完,函数内定义的变量都销毁了,但是我们有时候需要保存函数内的这个变量,每次在这个变量的基础上完成一些列的操作。
我们可以通过 闭包 来解决这个需求。
闭包的定义:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。
通过闭包的定义,我们可以得知闭包的形成条件:
# 1. 函数嵌套
def func_out():
# 外部函数
num1 = 10
def func_inner(num2):
# 内部函数
# 2. 内部函数必须使用了外部函数的变量
result = num1 + num2
print("结果:", result)
# 3. 外部函数要返回内部函数,这个使用了外部函数变量的内部函数称为闭包
return func_inner
# 获取闭包对象
# 这个new_func就是闭包
# 这里的new_func = func_inner
new_func = func_out()
# 执行闭包
new_func(1) # 结果: 11
new_func(10) # 结果: 20
通过上面的输出结果可以看出闭包保存了外部函数内的变量
num1
,每次执行闭包都是在num1 = 10
的基础上进行计算。
修改闭包内使用的外部函数变量使用 nonlocal
关键字来完成。
# 1. 函数嵌套
def func_out():
# 外部函数的变量
num1 = 10
def func_inner():
# 在闭包内修改外部函数的变量
# num1 = 20 # 本意是修改外部函数变量, 其实是在闭包内定义了一个局部变量
# 在闭包内修改外部函数的变量需要使用nonlocal关键字
nonlocal num1
num1 = 20
# 2.内部要使用外部函数的变量
result = num1 + 10
print("结果:",result)
print()
print("修改前的外部变量:", num1)
func_inner()
print("修改后的外部变量:", num1)
# 3. 返回内部函数
return func_inner
# 创建闭包对象
new_func = func_out()
new_func()
结果为:
修改前的外部变量: 10
结果: 30
修改后的外部变量: 20
结果: 30
需求: 根据配置信息使用闭包实现不同人的对话信息。
例如对话:
# 外部函数接收姓名参数
def config_name(name):
# 内部函数保存外部函数的参数,并且完成数据显示的组成
def inner(msg):
print(name + ":" + msg)
print(id(inner)) # id不同,创建的是两个不同的闭包
# 外部函数要返回内部函数
return inner
# 创建tom闭包实例(对象)
tom = config_name("tom")
# 创建jerry闭包实例
jerry = config_name("jerry")
# 如果执行tom闭包,因为已经保存了name参数,那么以后在输入的时候都是,tom说:xxx
tom("哥们,过来一下,我们一起玩耍!")
jerry("打死都不去!")
tom("我不吃你")
jerry("谁相信你")
结果为:
1712302326112
1712355599552
tom:哥们,过来一下,我们一起玩耍!
jerry:打死都不去!
tom:我不吃你
jerry:谁相信你
闭包还可以提高代码的可重用性,不需要再手动定义额外的功能函数。
闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。
注意:
- 由于闭包引用了外部函数的变量,外部函数的变量没有及时释放,消耗内存。
就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。
装饰器的功能特点:
我们知道装饰器本质上就是一个闭包,如果 闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器。
# 定义装饰器
def decorator(func): # 函数的参数有且仅有一个并且是函数类型
print("装饰器执行了")
def inner():
# 在内部函数里面对已有函数进行装饰
print("已添加登陆验证")
func()
return inner
# 已有函数
def comment():
print("发表评论")
# 调用函数,decorator(comment)其实就是inner,又因为“不修改已有函数的调用方式”,所以仍然将其赋值为comment
comment= decorator(comment)
comment()
输出:
装饰器执行了
已添加登陆验证
发表评论
注意:
- 闭包函数有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器。
- 写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展。
装饰器的语法糖写法
如果有多个函数都需要添加登录验证的功能,每次都需要编写 func = decorator(func)
这样代码对已有函数进行装饰,这种做法还是比较麻烦。
Python给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字
,通过语法糖的方式也可以完成对已有函数的装饰
# 定义装饰器
def decorator(func): # 函数的参数有且仅有一个并且是函数类型
print("装饰器执行了")
def inner():
# 在内部函数里面对已有函数进行装饰
print("已添加登陆验证")
func()
return inner
# 已有函数
@decorator
def comment():
print("发表评论")
comment()
输出:
装饰器执行了
已添加登陆验证
发表评论
@decorator
等价于comment = decorator(comment)
- 装饰器的执行时机: 当当前模块(当前py文件)加载完成以后,装饰器会立即执行,对已有函数进行装饰
装饰器实现已有函数执行时间的统计
import time
# 定义装饰器
def decorator(func):
def inner():
# 内部函数对已有函数进行装饰
# 获取时间距离1970-1-1:0:0:1的时间差
begin = time.time()
func()
end = time.time()
result = end - begin
print("函数执行完成耗时:", result)
return inner
@decorator # work = decorator(work), work = inner
def work():
for i in range(10000):
print(i)
work()
通用的装饰器:可以装饰任意类型的函数。
# 定义装饰器
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)
输出:
--正在努力计算--
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)
print("结果为:", result)
输出:
正在努力执行加法计算
结果为: 3
因为内部函数的类型和要装饰的已有函数的类型保持一致,当要装饰的已有函数有返回值,内部函数也需要有返回值。
# 装饰器
def decorator(func):
def inner(*args, **kwargs):
print("--正在努力计算--")
# *args:把元组里面的每一个元素按照位置参数的方式进行传参
# **kwargs:把字典里面的每一个键值对按照关键字参数的方式进行传参
num = func(*args, **kwargs)
return num
return inner
# 使用语法糖装饰函数
@decorator
def sum_num(*args, **kwargs):
result = 0
# args:元组类型
# kwargs:字典类型
for value in args:
result += value
for value in kwargs.values():
result += value
return result
sum_num(1, 2, a=10)
print("结果为:",result)
输出:
--正在努力计算--
结果为: 13
# 通用装饰器
def decorator(fn):
def inner(*args, **kwargs):
print("--正在努力计算--")
result = fn(*args, **kwargs)
return result
return inner
def make_div(func):
print("make_div装饰器执行了")
def inner():
# 在内部函数对已有函数进行装饰
result = "" + func() + ""
return result
return inner
# 定义装饰器
def make_p(func):
print("make_p装饰器执行了")
def inner():
# 在内部函数对已有函数进行装饰
result = ""
+ func() + ""
return result
return inner
# 多个装饰器的装饰过程: 由内到外的一个装饰过程,先执行内部的装饰器,在执行外部的装饰器
# 原理剖析: content = make_div(make_p(content))
# 分步拆解: content = make_p(content),内部装饰器装完成content=make_p.inner
# content = make_div(make_p.inner)
@make_div
@make_p
def content():
return "人生苦短,我用python!"
result = content()
print(result)
结果为:
make_p装饰器执行了
make_div装饰器执行了
人生苦短,我用python!
多个装饰器的装饰过程是:离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程
带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式:@装饰器(参数,...)
def return_decorator(flag):
# 装饰器, 装饰器只能接收一个参数并且是函数类型
def decorator(func):
def inner(a, b):
if flag == "+":
print("正在努力执行加法计算")
elif flag == "-":
print("正在努力执行减法计算")
func(a, b)
return inner
# 当调用函数的时候可以返回一个装饰器decorator
return decorator
# 加法计算
# decorator = return_decorator("+"), @decorator => add=decorator(add)
@return_decorator("+")
def add(a, b):
print(a + b)
add(1, 2)
# decorator = return_decorator("+")
# @decorator
# def add(a, b):
# print(a + b)
# add(1, 2)
输出结果:
正在努力执行加法计算
3
使用带有参数的装饰器,其实是在装饰器外面又包裹了一个函数,使用该函数接收参数,返回是装饰器,因为
@
符号需要配合装饰器实例使用
类装饰器,就是通过定义一个类来装饰函数。
class MyDecorator(object):
def __init__(self, func):
self.__func = func
# 实现__call__这个方法,让对象变成可调用的对象,可调用的对象能够像函数使用
def __call__(self, *args, **kwargs):
# 对已有函数进行封装
print("课已讲完")
self.__func()
@MyDecorator # @MyDecorator => show = MyDecorator(show)
# MyDecorator是一个类,show = MyDecorator(show) 相当于创建MyDecorator对象,创建对象时show这个参数没法接受,这时就需要__init__参数
def show():
print("快要下学啦")
# 执行show # 执行MyDecorator类创建实例对象 -> show() => 对象()
show()
# class AAA(object):
# pass
#
# a = AAA()
# a() # 对象是不可调用
注意:
@MyDecorator
等价于show = MyDecorator(show)
, 所以需要提供一个init
方法,并多增加一个func
参数。- 要想类的实例对象能够像函数一样调用,需要在类里面使用
call
方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。- 在
call
方法里进行对func
函数的装饰,可以添加额外的功能。
拓展: