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

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

Original: https://www.cnblogs.com/dack-zt-deng/p/15702449.html
Author: dack_deng
Title: Python-装饰器的入门讲解

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/714132/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 解决Visual Studio Code无法响应的问题

    最近发现我的Visual Studio Code总是莫名其妙的无响应,具体症状是:打开VS Code一段时间后,通过右键菜单打开Visual Studio Code新文件的时候,无…

    技术杂谈 2023年5月31日
    0103
  • 【iOS】ARC-MRC下的单例及其应用

    单例的应用十分普遍, 单例模式使一个类仅仅有一个实例。 *易于供外界訪问. *方便控制实例个数,节约系统资源. *OC 中的常见单例: 如:UIApplication,NSNoti…

    技术杂谈 2023年5月30日
    097
  • QQ音乐歌单获取所有歌名tempmonkey

    QQ音乐歌单获取所有歌名tempmonkey csharp;gutter:true; // ==UserScript== // @name 歌名获取 // @namespace h…

    技术杂谈 2023年5月31日
    083
  • CAIL2021-阅读理解任务-top3-数据预处理模块

    class SquadExample(object): """ A single training/test example for the Squa…

    技术杂谈 2023年6月1日
    0121
  • Linux命令

    linus/终端的常用快捷键 【ll】 显示当前目录的所有文件【详】【ls】 显示当前目录的所有文件【略】【ls /路径】显示该路径下的所有文件 【cd ..】 进入上级目录【./…

    技术杂谈 2023年7月24日
    094
  • 不经意传输(OT)-总结

    https://zhuanlan.zhihu.com/p/399361005 Oblivious Transfer 总结 ​ 不经意传输(OT,oblivious transfer…

    技术杂谈 2023年5月31日
    098
  • 架肩与拐肘

    内家拳大多讲究沉肩坠肘,太极拳也是,沉肩坠肘是费时功夫,成年人必须花一定的时间专门去纠正。沉肩坠肘解决不了,上肢的实战只能靠蛮力。 沉肩功夫出不来的人,力从地起的劲力很难直接自然性…

    技术杂谈 2023年5月31日
    0105
  • HTTP和Servlet快速入门

    依赖:创建web项目,导入Servlet依赖坐标 Tomcat内置Servlet,若运行时使用该依赖则会导致冲突使用 <scope>provided</scope…

    技术杂谈 2023年7月24日
    067
  • EducationalDPContest社论

    SoyTony 让我放歌词: Wish You Were Gay SoyTony 不让我放中文歌词, 《Wish You Were Gay》Baby, I don’t …

    技术杂谈 2023年7月24日
    049
  • 2022.24 判断职业方向好坏的两个方面

    如何判断一个职业方向好不好?可以从下面 2 个方向来判断: 1、天花板高度:你这个职业方向最厉害的那批人能够到达的高度,这通常是你将来最好情况下能达到的上限。然后尝试把这份工作的从…

    技术杂谈 2023年5月30日
    082
  • synchronized

    线程锁 1.1synchronized的认识 1.1.1synchronized的介绍 在多线程并发编程中,synchronized关键字是重量级锁的代名词。但是,随着JDK的发展…

    技术杂谈 2023年6月21日
    095
  • 负载均衡之keepalived

    备份和修改keepalived配置文件 DR配置文件: cp keepalive.conf keepalived.conf.bak cat /etc/keepalived.conf…

    技术杂谈 2023年7月11日
    070
  • Switch分支结构

    多重选择:switch语句 在处理多个选项时,使用多个 else/if 语句会使结构显得很臃肿。Java有一个和C/C++完全相同的switch语句。 例如,如果建立一个如下图所示…

    技术杂谈 2023年7月11日
    062
  • Vue3+Vue-cli4项目中使用腾讯滑块验证码

    Vue3+Vue-cli4项目中使用腾讯滑块验证码 简介: 滑块验证码相比于传统的图片验证码具有以下优点: 验证码的具体验证不需要服务端去验证,服务端只需要核验验证结果即可。 验证…

    技术杂谈 2023年6月21日
    095
  • Windows DIB文件操作具体解释-4.使用DIB Section

    前面讲了为了提高DIB的显示性能和效率,我们将DIB转换成DDB。可是这又遇到一个问题。假设我想操作DIB的数据的话,显然是不能使用DDB:一是由于DIB转DDB时发生了颜色转换。…

    技术杂谈 2023年5月31日
    084
  • jvm的简介

    什么是jvm? java虚拟机就是二进制字节码的运行环境。我们可以把jvm看做是运行在不同系统上的一个软件应用的计算机,就比如说我们要打开图片,就得用看图软件,或者我们要对文件进行…

    技术杂谈 2023年7月11日
    096
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球