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

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

posted @ 2021-12-19 20:44  dack_deng  阅读(159)  评论(1编辑  收藏  举报