• Python装饰器介绍(一)


    一. 预备知识

    1.1 Python函数中的可变参数

    • 函数参数可以分为形参(必填参数, 默认参数, 可变参数)和实参(位置参数, 关键字参数)
    • 可变参数能够支持函数接收任意数量的参数(实参)
    • *args表示收集所有多余的位置参数到一个args元组中, 其中argsarguments的缩写, 
    • *kwags表示收集所有多余的关键字参数到一个kwargs字典中, 其中kwargskeyword arguments的缩写
    # -*- coding: utf-8 -*-
    
    def test1(*args, **kwargs):
        print(f"这是额外的位置参数: {args}")
        print(f"这是额外的关键字参数: {kwargs}")
    
    
    t = ('a', 'b')
    d = {'c': 'c', 'd': 'd'}
    # *表示对元素解包; **表示对字典解包
    test1(*t, **d)  # 等价于: test1("a", "b", c="c", d="d")

    1.2 对闭包函数的理解

    • python中的函数有一些特点: 函数内可以定义函数; 函数可以作为参数被传递; 函数可以作为另一个函数的返回值
    • 闭包函数的本质其实是一个嵌套函数
    • 外层函数内层函数提供了一个运行环境, 内层函数被当成返回值返回给外层函数
    • 从python语法上, 可以通过函数的__closure__方法来判断一个函数是否为闭包函数
    # -*- coding: utf-8 -*-
    
    def test1():
        def inner1(m, n):
            return m + n
    
        return inner1
    
    
    def test2(m):
        def inner2(n):
            return m + n
    
        return inner2
    
    
    # [test1函数]不是闭包函数
    print(test1.__closure__)  # 运行结果: None
    
    # [test1函数]内的[inner1函数]是闭包函数
    inner1 = test1()
    print(inner1.__closure__)  # 运行结果: None
    
    # [test2函数]不是闭包函数
    print(test2.__closure__)  # 运行结果: None
    
    # [test2函数]内的[inner2函数]是闭包函数
    inner2 = test2(5)
    print(inner2.__closure__)  # 运行结果: (<cell at 0x02FED430: int object at 0x7989D470>,)

    二. 装饰器的介绍

    2.1 装饰器是python中一类比较有特色的语法, 用来装饰函数和类

    2.2 装饰器在不破坏原有函数结构的情况下, 可以进行功能扩展

    2.2 使用装饰器, 可以增加代码的可读性, 让代码层次结构变得更加清晰

    三. 装饰器的实现

    3.1 编写一个最简单的装饰器 

    实现代码:

    # -*- coding: utf-8 -*-
    
    # 被装饰函数
    def myfunc():
        return "hello chinablue!"
    
    
    # 装饰器: 在函数执行前
    def mylog(func):
        def inner():
            print(f"这是执行函数前打印的信息")
            return func()
    
        return inner
    
    
    print(mylog(myfunc)())

    执行结果:

    这是执行函数前打印的信息
    hello chinablue!

    3.2 编写一个通用装饰器

    实现功能: 

      同一个装饰器可以装饰参数个数不同的函数

    实现代码:

    # -*- coding: utf-8 -*-
    
    # 被修饰函数1
    def myfunc1(a, b):
        return a + b
    
    
    # 被修饰函数2
    def myfunc2(a, b, c):
        return a + b + c
    
    
    # 装饰器
    def mylog(func):
      # 运用可变参数后, 可以装饰任何带参数的函数
    def inner(*args, **kwargs): res = func(*args, **kwargs) print(f"执行结果为: {res}") return res return inner # myfunc1函数: 2个必填参数 mylog(myfunc1)(1, 2) # myfunc2函数: 3个必填参数 mylog(myfunc2)(1, 2, 3)

    执行结果:

    执行结果为: 3
    执行结果为: 6

    3.3 使用@语法糖方式调用装饰器

    实现代码:

    # -*- coding: utf-8 -*-
    
    # 装饰器
    def mylog(func):
        def inner(*args, **kwargs):
            res = func(*args, **kwargs)
            print(f"执行结果为: {res}")
            return res
    
        return inner
    
    
    @mylog
    def add(a, b):
        return a + b
    
    
    # 没有改变函数的调用方式
    add(1, 2)

    执行结果:

    执行结果为: 3

    3.4 装饰器会篡改原函数的属性

    因为使用装饰器, 原函数(即被装饰函数)的地址指向了装饰器所定义的内部inner函数

    # 打印add函数的属性
    print(f"add.__name__")  # 执行结果: inner
    print(f"add.__doc__")   # 执行结果: None

    改进代码:

    # -*- coding: utf-8 -*-
    
    from functools import wraps
    
    
    # 装饰器
    def mylog(func):
        # 可以借助functools.warps函数, 避免属性篡改问题
        @wraps(func)
        def inner(*args, **kwargs):
            res = func(*args, **kwargs)
            print(f"执行结果为: {res}")
            return res
    
        return inner
    
    
    @mylog
    def add(a, b):
        """
        功能: 实现加法运算
        :param a:
        :param b:
        :return:
        """
        return a + b
    
    
    # 打印函数的属性
    print(f"add函数的名字: {add.__name__}")
    print(f"add函数的注释信息: {add.__doc__}")
    
    add(1, 2)

    执行结果:

    add函数的名字: add
    add函数的注释信息: 
        功能: 实现加法运算
        :param a:
        :param b:
        :return:
        
    执行结果为: 3

    3.5 带参数的装饰器

    实现功能:

      实现一个可以设置日志等级的log装饰器

    思路分析:

      为了让装饰器能够传参, 需要再嵌套一层函数

     实现代码:

    # -*- coding: utf-8 -*-
    
    from functools import wraps
    
    
    # 装饰器
    def mylog(level="INFO"):
        def outter(func):
            @wraps(func)
            def inner(*args, **kwargs):
                res = func(*args, **kwargs)
                print(f"[{level}]函数{func.__name__}的执行结果为: {res}")
                return res
    
            return inner
    
        return outter
    
    
    @mylog(level="INFO")
    def add(a, b):
        return a + b
    
    
    @mylog(level="ERROR")
    def sub(a, b):
        return a - b
    
    
    # 打印函数的属性
    
    add(1, 2)
    sub(1, 2)

    执行结果:

    [INFO]函数add的执行结果为: 3
    [ERROR]函数sub的执行结果为: -1

    3.6 一个函数可以同时使用多个装饰器

    # -*- coding: utf-8 -*-
    
    from functools import wraps
    
    
    # 装饰器1
    def mylog1(func):
        @wraps(func)
        def inner(*args, **kwargs):
            res = func(*args, **kwargs)
            print(f"执行结果为: {res}")
            return res
    
        return inner
    
    
    # 装饰器2
    def mylog2(level="INFO"):
        def outter(func):
            @wraps(func)
            def inner(*args, **kwargs):
                res = func(*args, **kwargs)
                print(f"[{level}]函数{func.__name__}的执行结果为: {res}")
                return res
    
            return inner
    
        return outter
    
    
    @mylog2(level="DEBUG")
    @mylog1
    def add(a, b):
        return a + b
    
    
    # 多个装饰器的执行顺序: 从近到远依次执行
    add(1, 2)

    使用函数调用方式调用装饰器

    mylog2(level="ERROR")(mylog1(add))(1, 2)

    四. 本文小结

    1. 装饰器可以在不改变原函数代码,不改变原函数调用方式的情况下, 给函数添加扩展功能

    2. 装饰器的本质就是一个嵌套函数, 如果装饰器需要接收参数, 则需要增加一层函数嵌套

    3. 装饰器有两种调用方式: 1函数传参方式, 2使用@语法糖

    4. 装饰器的外层函数接收的是被装饰函数, 返回的是内层函数

    5. 装饰器的内层函数(即闭包函数)负责装饰被装饰函数

    6. 装饰器的常用场景有: 打印日志, 性能测试, 权限验证等 

     

  • 相关阅读:
    技术面试之经验总结
    为何只有两篇文章?
    LOJ6364 烂柯
    mysql批量更新数据(性能优化)
    一个对象的key引发的血案
    总结与元素坐标相关的属性(再也搞不混了)
    利用nodejs搭建服务器,测试AJAX
    初探jquery之强大丰富的选择器
    Web前端开发规范手册
    IE8下标签float导致的bug。
  • 原文地址:https://www.cnblogs.com/reconova-56/p/15148637.html
Copyright © 2020-2023  润新知