闭包与装饰器

章节目录:

    • 一、闭包
      • 1.1 相关概述
      • 1.2 基本使用
    • 二、装饰器
      • 2.1 相关概述
      • 2.1 装饰器最早版本
      • 2.3 基本使用
      • 2.4 装饰带有参数的函数
      • 2.5 装饰带有返回值的函数
      • 2.6 通用装饰器
      • 2.7 带有参数的装饰器
      • 2.8 类装饰器
      • 2.9 装饰器的使用场景
    • 三、装饰器总结
    • 四、结束语

一、闭包

Python 中的闭包是一种高级特性,它可以让我们更加灵活地使用函数。

1.1 相关概述

  • 闭包closure )指的是在函数内部定义了另外一个函数,并返回了这个内部函数作为函数对象,同时还保存了外层函数的状态信息。这个内部函数可以依赖外层函数的变量和参数,而且外层函数返回的是这个内部函数的引用。这种在函数内部定义函数并返回的方式称为闭包。
  • 简而言之,闭包就是能够读取外部函数内的变量的函数。
  • 闭包的作用
    • 闭包是将外层函数内的局部变量和外层函数的外部连接起来的一座桥梁
    • 将外层函数的变量持久地保存在内存中。
  • 构成条件
    • 函数嵌套(函数里面在定义函数)的前提下。
    • 内部函数使用了外部函数的变量(还包括外部函数的参数)。
    • 外部函数返回了内部函数。

1.2 基本使用

  • 步骤思路

    • 定义外部函数。
    • 定义外部函数,在内部函数中使用外部函数的变量。
    • 外部函数返回内部函数的地址。
  • 代码示例

# 1.定义外部函数。
def outer(x):
    # 2.定义内部函数。(内部函数使用到外部函数的变量。)
    def inner(y):
        print(f"outer_x={x} ,inner_y={y}")
        return x + y

    # 3.外部函数返回内部函数。
    return inner


if __name__ == '__main__':
    # 创建闭包实例。
    o = outer(5)

    # 调用闭包。
    print(o(3))
    # outer_x=5 ,inner_y=3
    # 8

  • 优点
    • 变量长期驻扎在内存中,可以重复使用变量。
    • 避免全局变量的污染。
    • 私有成员的存在。
  • 缺点
    • 由于是常驻内存, 则会增加内存开销 ,使用不当甚至会造成内存的泄露,所以不能滥用闭包
    • 解决方法是在退出函数之前,将不使用的变量全部删除

二、装饰器

装饰器本身就是一个闭包,它可以保留被装饰函数的状态信息,并在被装饰函数执行前后添加额外的功能

2.1 相关概述

  • 装饰器的作用
    • 不修改已有函数的源码
    • 不修改已有函数的调用方式
    • 给函数添加功能

2.1 装饰器最早版本

  • 代码示例
def check(fn):
    def inner():
        # 添加一个登录验证的功能。
        print("请先登录....")
        # 调用原函数。
        fn()

    return inner


def comment():
    print("发表评论")


if __name__ == '__main__':
    # 使用装饰器来装饰函数。
    comment = check(comment)
    comment()
    # 请先登录....
    # 发表评论

  • Python 给提供了一个装饰函数更加简单的写法,那就是语法糖,语法糖的书写格式是: @装饰器名字,通过语法糖的方式也可以完成对已有函数的装饰

2.3 基本使用

  • 代码示例(语法糖)
# 定义一个装饰器。
def browser(fn):
    # 内部函数:实现需要添加的新功能。
    def open():
        print("打开浏览器...")
        # 调用原函数。
        fn()
        print("关闭浏览器!")

    # 返回内部函数。
    return open


@browser
# @browser为语法糖等同于:login = browser(login)
def login():
    print("登录...")


if __name__ == '__main__':
    login()
    # 打开浏览器...
    # 登录...
    # 关闭浏览器!

2.4 装饰带有参数的函数

  • 代码示例
def logger(fn):
    """
    装饰带有参数的函数。
    :param fn:
    :return:
    """

    def inner(a, b):
        print("--- 开始计算 ---")
        fn(a, b)

    return inner


@logger
def my_sum(a, b):
    print(a + b)


if __name__ == '__main__':
    my_sum(3, 6)
    # --- 开始计算 ---
    # 9

2.5 装饰带有返回值的函数

  • 不管原函数有没有返回值,装饰器的内部函数都应该将原函数的返回值进行返回
  • 如果原函数有返回值,返回的就是原函数的返回值。
  • 如果没有返回的是 None
  • 代码示例
def logger(fn):
    """
    装饰带有返回值的函数。
    :param fn:
    :return:
    """

    def inner(a, b):
        print("--- 开始计算 ---")
        result = fn(a, b)
        return result

    return inner


@logger
def my_sum(a, b):
    return a + b


if __name__ == '__main__':
    print(my_sum(3, 6))
    # --- 开始计算 ---
    # 9

2.6 通用装饰器

  • 代码示例
def decorate(fn):
    """
    接受任意参数的装饰器。
    :param fn:
    :return:
    """

    def inner(*args, **kwargs):
        # TODO
        result = fn(*args, **kwargs)
        return result

    return inner

  • 函数 inner() 接受任意数量的位置参数和关键字参数。
  • 调用原始函数 fn 并返回其结果。

2.7 带有参数的装饰器

带有参数的装饰器就是使用装饰器装饰函数的时候可以传入指定参数,语法格式: @装饰器(参数,...)

  • 代码示例
def logger(flag):
    """
    带有参数的装饰器。
    :param flag:
    :return:
    """

    def decorator(fn):
        def inner(a, b):
            # 根据装饰器参数,有不同的日志提示功能。
            if flag == "+":
                print("--- 开始加法计算 ---")
            elif flag == "-":
                print("--- 开始减法计算 ---")
            else:
                raise Exception("参数不合法!")
            return fn(a, b)

        return inner

    return decorator


@logger("+")
def my_sum(a, b):
    return a + b


@logger("-")
def my_sub(a, b):
    return a - b


if __name__ == '__main__':
    print(my_sum(3, 6))
    # --- 开始加法计算 ---
    # 9
    print(my_sub(6, 3))
    # --- 开始减法计算 ---
    # 3

2.8 类装饰器

装饰器还有一种特殊的用法就是类装饰器,就是通过定义一个类来装饰函数。

  • 类装饰器就是使用类来实现的装饰器。它们通常通过在类中定义 __call__() 方法来实现。
  • 当我们使用 @ 语法应用装饰器时,Python 会调用装饰器类的 __init__() 方法创建一个实例,然后将被装饰的函数或类作为参数传递给 __init__() 方法。
  • 当被装饰的函数或方法被调用时,Python 会调用装饰器实例的 __call__() 方法。
  • 代码示例
class Logger(object):
    def __init__(self, fn):
        # 将被装饰的函数 fn 保存在实例属性 fn 中。
        self.__fn = fn

    def __call__(self, *args, **kwargs):
        """
        实现__call__方法,表示把类像调用函数一样进行调用。
        :param args:
        :param kwargs:
        :return:
        """
        # 添加装饰功能。
        print("开始记录日志信息...")
        self.__fn()


@Logger
def login():
    print("完成登录")


if __name__ == '__main__':
    login()
    # 开始记录日志信息...
    # 完成登录

  • 调用 login() 方法时,本质上是在调用 __call__() 方法。
  • 相比函数装饰器,类装饰器有几个主要优势
    • 更好的组织:类装饰器可以利用 Python 的面向对象特性,将相关的方法和数据封装在一起,这使得代码更易于理解和维护。
    • 更大的灵活性:类装饰器可以利用继承来复用和扩展代码。例如,你可以创建一个基础的装饰器类,然后通过继承这个类来创建特定的装饰器。
    • 更好的控制:类装饰器可以使用实例变量来保存状态。这在一些需要保存状态的装饰器(例如计数器或缓存)中非常有用。

2.9 装饰器的使用场景

  • 统计程序的执行时间:可以通过编写一个装饰器来记录函数的开始时间和结束时间,然后计算函数执行的时间。这个装饰器可以被用于优化程序性能或者进行调试。
import time


def time_cost(fn):
    def inner():
        begin = time.time()
        fn()
        # 当前时间减去开始时间。
        cost = time.time() - begin
        print(f"函数执行花费={cost}s")

    return inner


@time_cost
def sample():
    # 线程睡眠 2s。
    time.sleep(2)


if __name__ == '__main__':
    sample()
    # 函数执行花费=2.003966808319092s

  • 辅助系统功能输出日志信息:可以编写一个装饰器来记录函数的执行过程以及输出日志信息,这个装饰器可以被用于调试、监控和错误处理等方面。
def logger(fn):
    def inner(id):
        print("开始记录日志信息...")
        print(f"用户查询id={id}")
        result = fn(id)
        print(f"用户查询结果={result}")
        print(f"日志记录完成,准备返回结果!")
        return result

    return inner


@logger
def query(user_id):
    msg = {}
    if user_id is not None:
        msg = {
            "user": "xxx",
            "age": 18
        }
    return msg


if __name__ == '__main__':
    query(111)
    # 开始记录日志信息...
    # 用户查询id=111
    # 用户查询结果={'user': 'xxx', 'age': 18}
    # 日志记录完成,准备返回结果!

三、装饰器总结

  1. Python 的装饰器和 Java 的注解(Annotation并不是同一回事,和 C# 中的特性(Attribute)也不一样,完全是两个概念。
  2. 装饰器的理念是对原函数、对象的加强,相当于重新封装,所以一般装饰器函数都被命名为 wrapper(),意义在于包装。函数只有在被调用时才会发挥其作用。比如 @logging 装饰器可以在函数执行时额外输出日志,@cache 装饰过的函数可以缓存计算结果等等。
  3. 而注解和特性则是对目标函数或对象添加一些属性,相当于将其分类。这些属性可以通过反射拿到,在程序运行时对不同的特性函数或对象加以干预。比如带有 Setup 的函数就当成准备步骤执行,或者找到所有带有TestMethod 的函数依次执行等等。

四、结束语


“-------怕什么真理无穷,进一寸有一寸的欢喜。”

微信公众号搜索:饺子泡牛奶

你可能感兴趣的:(Python,python)