百木园-与人分享,
就是让自己快乐。

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()

关于装饰器的知识,到此全部讲完啦,希望对大家有所帮助! 如果有不对的地方,欢迎各位小伙伴指出,感谢!

来源:https://www.cnblogs.com/dack-zt-deng/p/15702449.html
图文来源于网络,如有侵权请联系删除。

未经允许不得转载:百木园 » Python-装饰器的入门讲解

相关推荐

  • 暂无文章