目录
前言
一、闭包函数
1.闭函数
2.包函数
3.闭包函数
4.闭包函数实例
实例一:
实例二:
二、装饰器
1.装饰器的功能介绍
2.装饰器的实现方式
(1)首先我们来看以下的源代码,模拟《王者荣耀》的开局场景。
(2)为了进一步优化,要求为该游戏更新一项新需求,使其能够统计该功能的运行时间。
方案一:
方案二:
方案三:
方案四:
方案五:
方案六:
方案七:
优化:
3.闭包函数和装饰器的区别
什么是闭包函数?什么是装饰器?闭包函数和装饰器有什么区别?闭包函数的作用是什么?装饰器的作用是什么?
def f1():
# 到目前为止这个f1肯定是没有被封闭起来的,因为它现在属于全局
def f2():
pass
# 但是如果在f1里面定义一个f2,这个f2现在就是被封起来了
# 因为这个f2现在只能在f1的里面局部访问,故此这个f2就叫做《闭函数》
pass
def f1():
x = 10
def f2():
# 比如要在原有的包函数上内部打印一个 x
# 然后在外层函数内部定义一个 x=10
print(x)
# 这就是在f2的内部引用了外层函数f1的局部作用域的名字,故此叫做《包函数》
pass
def f1():
x = 10
def f2():
print(x)
# 函数是可以传递的,可以当做返回值被另一个函数返回
# 如此可以在这里直接f2 return 出去
return f2 # 这里f2一定不要加括号,加括号就是返回f2的返回值了
# 不加括号返回的才是f2的内存地址
# 现在全局想要访问f2的内存地址就很容易了
# 直接调用f1
res = f1() # 这个f1返回的就是f2的内存地址了,现在用一个res来接着
print(res) # 输出结果:.f2 at 0x000002C2EE3754C8>
# f1.local:就是f1局部的f2的内存地址
# 既然res拿到了f2的内存地址,我们便可以用括号来调用它
res() # 输出结果:10
>>>输出结果:
.f2 at 0x000002140F1854C8>
10
Process finished with exit code 0
假如:现在我们在 res() 前面加一个 x=20
def f1():
x = 10
def f2():
print(x)
return f2
res = f1()
x = 20 # 1.现在我们在 res() 前面加一个 x=20
res() # 2.这个时候的输出结果依旧还是f1内部的10
# 3.原因是名字的查找顺序是在定义阶段就确定好了的,和我们在哪里调用是没有关系的
# 4.不管我们在哪里调用他要找的 x 永远都是它自己包里面的那个 x
>>>输出结果:
10
Process finished with exit code 0
故此我们要想在外部实现应该如下:
def f1(x): # 2.我直接把这个 x 作为 f1 的参数
# x = 10 # 1.我们在这里写 x=10 也就是相当于把x写死了,故此将他注释掉重新修改
def f2():
print(x)
return f2
res = f1(30) # 3.然后外面调用 f1 的时候,给他传一个 10 就可以了
x = 20
res() # 4.故此后面再调用的时候,这个f2内部用的就是我们传给f1的值了
>>>输出结果:
30
Process finished with exit code 0
f1
下定义了一个内部函数 f2,并且外部函数的返回值就是内部函数,同时在内部函数中,我们引用到了外部函数的变量 x ,而闭包的作用就是可以将外层函数的变量保存在内存中而不被销毁(1)什么时候需要用到闭包函数?
需求:创建一个函数add(),每次调用该函数都只能传一个数 num ,每次返回的结果都是基于上一次结果的值 sum 进行累加操作。例如,sum的默认值为0,如果我们依次调用 add(10)、add(20)、add(30) 后,期望得到的最终结果是 sum = 60。
(1)对于该问题,因为需要在函数内部对函数外部的变量进行处理,我们可能会考虑使用 global 来处理。(点我快速了解global)
sum = 0
def get_add_sum(num):
global sum
sum += num
return sum
print(get_add_sum(10)) # 输出:10
print(get_add_sum(20)) # 输出:30
print(get_add_sum(30)) # 输出:60
print(sum) # 输出:60
(2)对于上面的问题,除了使用全局变量外,我们还可以通过 闭包 来实现。
sum = 0
def get_add_sum(sum): # 外层函数
def add_num(num): # 内层函数
nonlocal sum
sum += num
return sum
return add_num
# 闭包的作用:以将外层函数的变量保存在内存中而不被销毁。
# 简单点来说每add一次它就会自己记住这一次的数字,下次读取的时候再调用
add = get_add_sum(sum)
print(add(10)) # 输出:10
print(add(20)) # 输出:30
print(add(30)) # 输出:60
print(sum) # 输出:0
def outer():
res = []
for i in range(3):
print("外部的i值:{}".format(i))
def inner(x):
print("内部的i值:{}".format(i))
return i + x
res.append(inner)
return res
temp = outer()
# 写法一:
# res = [i(10) for i in temp]
# 写法二:
res = []
for i in temp:
res.append(i(10)) # 此时的 i(10) 可以理解为 inner(10)
print(res)
>>>输出结果:
外部的i值:0
外部的i值:1
外部的i值:2
内部的i值:2
内部的i值:2
内部的i值:2
[12, 12, 12]
Process finished with exit code 0
(1)在这里,我们可以使用闭包来保存函数的外部变量,修改代码如下:
def outer(i):
print("外部的i值:{}".format(i))
def inner(x):
print("内部的i值:{}".format(i))
return i + x
return inner
def demo():
res = []
for i in range(3):
res.append(outer(i))
return res
temp = demo()
res = [i(10) for i in temp]
print(res)
>>>输出结果:
外部的i值:0
外部的i值:1
外部的i值:2
内部的i值:0
内部的i值:1
内部的i值:2
[10, 11, 12]
Process finished with exit code 0
器:器具、工具(编程中我们需要创造一个函数的时候也就是相当于创建一个工具)
装饰:为相关事物添加额外功能
装饰器:在不修改装饰对象的源代码,也不修改调用方式的前提下定义一个函数(或者类),这个函数的功能就是用来装饰其他函数的,也即是说这个函数是用来给其他函数添加额外功能的
(1)为什么需要装饰器?而不是在原代码上修改?
import time
def inside(group, s):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
inside('红', 5)
print('---'*20)
inside('蓝', 3)
>>>输出结果:
欢迎来的王者荣耀
你出生在红方阵容
敌军还有5秒到达战场
全军出击
------------------------------------------------------------
欢迎来的王者荣耀
你出生在蓝方阵容
敌军还有3秒到达战场
全军出击
Process finished with exit code 0
import time
def inside(group, s):
star = time.time()
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
end = time.time()
print(f'用时{int(end-star)}秒')
inside('红', 3)
>>>输出结果:
欢迎来的王者荣耀
你出生在红方阵容
敌军还有3秒到达战场
全军出击
用时3秒
Process finished with exit code 0
import time
def inside(group, s):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
star = time.time()
inside('红', 3) # 要统计该功能的运行时间,
# 直接在外部调用的代码上下直接统计就可以,避免了修改源代码的隐患。
end = time.time()
print(f'用时{int(end-star)}秒')
>>>输出结果:
欢迎来的王者荣耀
你出生在红方阵容
敌军还有3秒到达战场
全军出击
用时3秒
Process finished with exit code 0
import time
def inside(group, s):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
def wrapper_inside():
star = time.time()
inside('红', 3) # 要统计该功能的运行时间,
# 直接在外部调用的代码上下直接统计就可以,避免了修改源代码的隐患。
end = time.time()
print(f'用时{int(end-star)}秒')
wrapper_inside()
>>>输出结果:
欢迎来的王者荣耀
你出生在红方阵容
敌军还有3秒到达战场
全军出击
用时3秒
Process finished with exit code 0
import time
def inside(group, s):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print('全军出击')
# 2.因为wrapper函数里面没有 xy
def wrapper_inside(x, y): # 所以我们需要将xy定义wrapper的形参
star = time.time()
inside(x, y) # 1.将这里改写成 x, y
end = time.time()
print(f'用时{int(end-star)}秒')
wrapper_inside('蓝色', 3) # 3.调用wrapper的时候给他传值就可以了
>>>输出结果:
欢迎来的王者荣耀
你出生在蓝色方阵容
敌军还有3秒到达战场
全军出击
用时3秒
Process finished with exit code 0
问题:假设以后inside函数的功能本身就要改变,比如我们要加入一个参数 z,然后print里面改为 print(f'{z}出击'),这样一下来的话 wrapper的形参要改,调用wrapper的时候也要改。
import time
def inside(group, s, z):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
# 1.在形参这里写一个*args和**kwargs
def wrapper_inside(*args, **kwargs): # 3.但是wrapper接收的参数是给inside用的
star = time.time()
inside(*args, **kwargs) # 4.所以inside也要改写
end = time.time()
print(f'用时{int(end-star)}秒')
wrapper_inside('蓝色', 3, '炮车') # 2.这样子写了之后不管怎么给wrapper传参数它都可以正常接收
# 5.所以当我们给wrapper传参的时候是要遵循其所对应的函数(inside)的形参规则的
# 6.所以再次调用wrapper的时候就参照inside的形参规则就行了,我们这里给它加上一个“炮车”
>>>输出结果:
欢迎来的王者荣耀
你出生在蓝色方阵容
敌军还有3秒到达战场
炮车出击
用时3秒
Process finished with exit code 0
问题:wrapper是用来统计被装饰对象的运行时间的,但是我们在wrapper的内部把装饰对象给写死了,固定是inside了。假如存在另一个函数recharge,刚好也需要统计他的运行时间,那么如果继续沿用该方法的话就需要再写一个类似wrapper的方法,也就又出现没必要的重复代码了。
import time
def inside(group, s, z):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
# 19.把这个func定义成outer的参数 将原来的 def outer(): 改为def outer(func):
def outer(func):
# def outer(): # 10.定义一个外包函数将整体包起来
# 16.但是我们看outer内部,我们再次吧func写死了 固定了只能是inside,
# 17.为了以后可以把它装饰给其他函数,我们要把它写活,因此注释掉 func = inside 改写
# 18.func = inside # 8.既然wrapper需要一个func,就直接定义一个变量func
# 9.直接将inside赋值给它
def wrapper_inside(*args, **kwargs): # 11.原来该函数是在全局的,
# 12.可以直接调用,但是现在变成局部了全局访问不到了
star = time.time()
func(*args, **kwargs) # 1.因此需要将该函数改为变量func
# 2.但是这样就会引发新的问题,因为wrapper函数为了把被装饰对象写活把inside替换成为了func
# 3.导致需要向wrapper的内部传一个参数func。
end = time.time()
print(f'用时{int(end-star)}秒')
return wrapper_inside # 因此我们需要将它return出来
# wrapper_inside('蓝色', 3, '炮车') # 13.因此我们需要引用该方法的时候就需要改写,故此这里注释掉
# wrapper_inside('蓝色', 3, '炮车', inside) 4.但是我们不能在这里直接传
# 5.因为这里的参数都是直接导进func所代表的函数内部的也就是inside内部
# 6.'蓝色', 3等等这些函数都是给inside用的,并不会停留在wrapper
# 7.因此,我们可以运用闭包函数的知识
# res = outer() # 14.因为outer()返回的是wrapper,所以我们用一个res来接住
# 15.但是现在还有一个问题,就是wrapper需要一个变量func,我们是通过outer包给它的
res = outer(inside)# 20.将原来的res = outer() 改为 res = outer(inside)
# 21.现在这个装饰器就被我们写活了
# 22.然后我们根据inside的形参来运行
res('蓝色', 3, '炮车')
>>>输出结果:
欢迎来的王者荣耀
你出生在蓝色方阵容
敌军还有3秒到达战场
炮车出击
用时3秒
Process finished with exit code 0
问题:还是修改了inside的调用方式,原来是调用inside现在变成了res
import time
def inside(group, s, z):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
def outer(func):
def wrapper_inside(*args, **kwargs):
star = time.time()
func(*args, **kwargs)
end = time.time()
print(f'用时{int(end - star)}秒')
return wrapper_inside
# res = outer(inside) # 1.既然这个地方可以赋值给res,同样也可以赋值给abcd
# 2.所以,将原来的注释掉,我们把它赋值给inside,
inside = outer(inside)
inside('蓝色', 3, '炮车') # 3.现在我们单独看这一行,对于原来的作者是不是就不知道发发生了什么
# 4.以为是原来的inside,但实际上早已经被我们偷梁换柱了,不在是当初的inside
# 5.到这里装饰器的基本功能就被我们实现了
# 装饰器:在不修改装饰对象的源代码,也不修改调用方式的前提下定义一个函数(或者类),
# 这个函数的功能就是用来装饰其他函数的,也即是说这个函数是用来给其他函数添加额外功能的
>>>输出结果:
欢迎来的王者荣耀
你出生在蓝色方阵容
敌军还有3秒到达战场
炮车出击
用时3秒
Process finished with exit code 0
现在我们对该功能再优化一下,假如我们的原功能是有返回值的该怎么办呢?例如:
def inside(group, s, z):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
return '王者荣耀正在运行!'
步骤一:
我们先直接print一下看看
import time
def inside(group, s, z):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
return '王者荣耀正在运行!'
def outer(func):
def wrapper_inside(*args, **kwargs):
star = time.time()
func(*args, **kwargs)
end = time.time()
print(f'用时{int(end - star)}秒')
return wrapper_inside
inside = outer(inside)
print(inside('蓝色', 3, '炮车'))
>>>输出结果:
欢迎来的王者荣耀
你出生在蓝色方阵容
敌军还有3秒到达战场
炮车出击
用时3秒
None
Process finished with exit code 0
可以看到我们print的结果是None,为什么呢?原因很简单,我们这里的inside接收的是outer(inside)中wrapper的内存地址。然后我们看看wrapper此时是不是没有返回值,是不是没有return?因此我们还需要对它进行修改。
def wrapper_inside(*args, **kwargs):
star = time.time()
func(*args, **kwargs)
end = time.time()
print(f'用时{int(end - star)}秒')
步骤二:
import time
def inside(group, s, z):
print('欢迎来的王者荣耀')
print(f'你出生在{group}方阵容')
print(f'敌军还有{s}秒到达战场')
time.sleep(s)
print(f'{z}出击')
return '王者荣耀正在运行!'
def outer(func):
def wrapper_inside(*args, **kwargs):
star = time.time()
# func(*args, **kwargs)
# 4.故此我们用response将func接住,我们把原来的注释掉
response = func(*args, **kwargs)
end = time.time()
print(f'用时{int(end - star)}秒')
# 1.首先我们需要明白我们需要返回的是原inside中的值,
# 2.而此时的inside是赋值在func中的,因此此时的func也即是inside的化身
# 3.所以这里外面返回的时候自然也即是func中的值
return response # 5.然后再将response返回
return wrapper_inside
inside = outer(inside)
print(inside('蓝色', 3, '炮车'))
>>>输出结果:
欢迎来的王者荣耀
你出生在蓝色方阵容
敌军还有3秒到达战场
炮车出击
用时3秒
王者荣耀正在运行!
Process finished with exit code 0
可以看到无论被装饰对象是有具有返回值都不影响整个程序的运行,同时也满足了装饰器所应具备的各种要求。到这里一个完整的装饰器就提现出来了
在Python中,闭包传递的参数是变量,装饰器传递的参数是函数对象,它们只是在传参内容上有不同。那么装饰器是不是属于闭包的一种呢,我们要怎么判断一个函数是否是闭包呢?
def demo_outer(x):
"""
闭包
"""
def demo_inner(y):
print("x的值:{}, y的值:{}, x + y 的值:{}".format(x, y, x + y))
return demo_inner
def demo_decorator(func):
"""
装饰器
"""
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper
def method():
pass
do = demo_outer(5) # 闭包
print(f"闭包的属性:{do.__closure__}")
dd = demo_decorator(method) # 装饰器
print("装饰器的属性:{}".format(dd.__closure__))
>>>输出结果:
闭包的属性:(,)
装饰器的属性:(,)
Process finished with exit code 0)
| |
所以结合上面的结果,我们也可以这样理解:装饰器本质上就是一个闭包函数,它只是一个传递函数对象的闭包函数。
声明:以上内容仅供学习交流,如有错误欢迎指正!!!