Python-装饰器的入门讲解
装饰器的万能公式
小白在学习装饰器时,会遇到一些地方不太理解或者不太清楚,这是因为一开始你就直接撸装饰器的缘故,那么怎样才能将装饰器理解并且弄懂呢?
所以在学装饰器之前必须要弄懂函数的嵌套以及闭包,接下来我用嵌套--->闭包--->装饰器这个顺序来讲解,希望对各位有用。
函数的嵌套
1、首先要弄懂,函数的作用域的概念:Python中以函数为作用域,在作用域中定义的相关数据只能被当前作用域或子作用域使用。
同时函数也是定义在作用域中的数据,在执行函数时候,也同样遵循:优先在自己作用域中寻找,没有则向上一作用域寻找
def func():
print("你好")
func()
def execute():
print("开始")
func()
print("结束")
def func():
print(666)
func()
execute()
认真执行上面的代码,你就能发现函数在全局作用域中的一些规则。也就是函数名相同的函数,新的函数会覆盖就的函数。
2、上述示例中的函数均定义在全局作用域,其实函数也可以定义在局部作用域,这样函数被局部作用域和其子作用于中调用(函数的嵌套)。
def func():
print("1111111111")
def inner():
print("-------------")
def handler():
print("222222222")
def inner():
print("333333333")
inner()
func()
print("444444444")
handler()
分析上面可知:函数先调用自己作用域内部的函数,若作用域内部没有,则从外部去找。
现在的你可能有疑问:为什么要这么嵌套定义?把函数都定义在全局不好吗?
其实,大多数情况下我们都会将函数定义在全局,不会嵌套着定义函数。不过,当我们定义一个函数去实现某功能,想要将内部功能拆分成N个函数,又担心这个N个函数放在全局会与其他函数名冲突时(尤其多人协同开发)可以选择使用函数的嵌套。
嵌套引发的作用域的问题
1、基于内存和执行过程分析作用域。
直接看2个例子,基本就可以理解了,我这边并没有去运行,可以的话,大家最好去亲自运行,看看与自己理解的是不是一样。
这边要注意一个问题:外部函数在return返回内部的函数名时,并没有执行函数,而是只返回函数名,仅此而已。看下面的例子:
name = "dack"
def run():
name = "dack_deng"
def inner():
print(name)
return inner # run函数仅仅只是返回的一个函数名而已
v1 = run() # 现在v1是执行了run()函数,返回inner这个函数名,即v1==inner
v1() # v1()==inner(),开始执行函数inner,输出“dack_deng”
v2 = run() # 同理
v2()
3、总结:
三句话搞定作用域:
- 优先在自己的作用域找,自己没有就去上级作用域。
- 在作用域中寻找值时,要确保此次此刻值是什么。
- 分析函数的执行,并确定函数
作用域链
。(函数嵌套)
闭包
闭包,简而言之就是将数据封装在一个包(区域)中,使用时再去里面取。(本质上闭包是基于函数嵌套搞出来一个特殊的嵌套)
也是看下面2个例子:
"""例子1"""
def task(arg):
def inner():
print(arg)
return inner # 函数仅仅只是返回inner函数名而已
v1 = task(11) # v1 == inner,此时agr == 11
v2 = task(22) # v2 == inner,此时agr == 22
v3 = task(33) # v3 == inner,此时agr == 33
v1() # 调用函数inner(),此时agr == 11,输出:11
v2() # 调用函数inner(),此时agr == 22,输出:22
v3() # 调用函数inner(),此时agr == 33,输出:33
"""例子2"""
def task(arg):
def inner():
print(arg)
return inner
inner_func_list = []
for val in [11,22,33]:
inner_func_list.append( task(val) ) # 执行玩for循环之后:inner_func_list == [inner, inner, inner]
inner_func_list[0]() # 11 # 函数调用inner_func_list中的第一个函数,并且第一函数调用时arg==11,输出:11
inner_func_list[1]() # 22 # 函数调用inner_func_list中的第一个函数,并且第一函数调用时arg==22,输出:22
inner_func_list[2]() # 33 # 函数调用inner_func_list中的第一个函数,并且第一函数调用时arg==33,输出:33
装饰器的万能公式
通过上面的函数嵌套和闭包的理解,接下来讲装饰器基本更好理解了。
装饰器就是在不修改原函数内容的前提下,通过@函数可以实现在函数前后自定义执行一些功能(批量操作会更有意义),类似于自动化测试中的前置和后置,这就更好理解了它是个啥东西了。
话不多说,先将装饰器之前,先上装饰器的万能公式,所有的装饰器都是基于这个公式去套用的。
"""装饰器的万能公式"""
def outer(origin):
def inner(*args, **kwargs): # 这里的*args和**kwargs不固定,可根据自己的项目中去指定
# 执行前
res = origin(*args, **kwargs) #调用原来的func函数
# 执行后
return res
return inner
@outer # 一定要记住这个:func = outer(func) ==========> func == innner
def func():
pass
func()
接下来直接上题:
"""1, 请为以下所有函数编写一个装饰器,添加上装饰器后可以实现,执行func时,先执行func函数内部代码
再输出”after“"""
def outer(origin):
def inner(*args, **kwargs):
res = origin(*args, **kwargs)
print("after")
return res
return inner
@outer
def func(a1):
print("func")
return a1 + "哈哈"
@outer
def base(a1, a2):
print("base")
return a1 + a2 + "呵呵"
@outer
def foo(a1, a2, a3, a4):
print("foo")
return a1 + a2 + a3 + a4 + "嘻嘻"
func("1")
base("1", "2")
foo("1","2","3","4")
"""2, 编写装饰器,添加上装饰器后实现:将被装饰的函数执行5次,将每次执行函数的结果按照顺序放到
列表中,最终返回列表"""
import random
def outer(origin):
def inner(*args, **kwargs):
print("before")
res = origin(*args, **kwargs)
print("after")
return res
return inner
@outer
def func():
return random.randint(1, 4)
list = []
for i in range(5):
print(i)
result = func()
list.append(result)# 内部自动执行5次,并将每次执行的结果追加到列表最终返回给result
print(list)
"""3, 编写函数装饰器,添加上装饰器后可以实现:检查文件所在路径(文件夹)是否存在,如果不存在
自动创建文件夹(保证写入文件不报错)"""
import os
file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'usr', 'bin', 'xxx')
def outer(origin):
def inner(*args, **kwargs):
# 函数执行前的操作
if not os.path.exists(file_path):
os.makedirs(file_path)
res = origin(*args, **kwargs) # 执行被装饰的原函数
# 执行完函数的下一步操作
return res
return inner
@outer
def writer_user_info(path):
with open(path, mode='w', encoding='utf-8') as file_obj:
for i in range(10):
file_obj.write("dack\n")
writer_user_info('usr/bin/xxx/xxx.txt')
总结:
我的那种写法就称为装饰器。
-
实现原理:基于@语法和函数闭包,将原函数封装在闭包中,然后将函数赋值为一个新的函数(内层函数),执行函数时再在内层函数中执行闭包中的原函数。
-
实现效果:可以在不改变原函数内部代码 和 调用方式的前提下,实现在函数执行和执行扩展功能。
-
适用场景:多个函数系统统一在 执行前后自定义一些功能
装饰器的重要补充:functools
先看下面这些实例:
"""示例1"""
def handler():
pass
handler()
print(handler.__name__) # handler
"""示例2"""
def auth(func):
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@auth
def handler():
pass
handler()
print(handler.__name__) # inner
"""示例3"""
import functools
def auth(func):
@functools.wraps(func)
def inner(*args, **kwargs):
return func(*args, **kwargs)
return inner
@auth
def handler():
pass
handler()
print(handler.__name__) # handler
所以,一般情况下大家不用functools也可以实现装饰器的基本功能,但后期在项目开发时,不加functools会出错(因为内部会读取__name__
,且__name__
重名的话就报错),所以在此大家就要规范起来自己的写法。
所以装饰器的万能公式,我们就可以更新如下所示:
import functools
def outer(func):
@functools.wraps(func)
def inner(*args, **kwargs):
# 函数执行前的操作
res = func(*args, **kwargs) # 执行原函数
# 函数执行后的操作
return res
return inner
@outer # func = outer(func)
func()
pass
func()
关于装饰器的知识,到此全部讲完啦,希望对大家有所帮助! 如果有不对的地方,欢迎各位小伙伴指出,感谢!