Python进阶学习笔记(三)—— 闭包和装饰器

1 闭包的定义


我们前面已经学过了函数,我们知道当函数调用完,函数内定义的变量都销毁了,但是我们有时候需要保存函数内的这个变量,每次在这个变量的基础上完成一些列的操作。

我们可以通过 闭包 来解决这个需求。

闭包的定义:在函数嵌套的前提下,内部函数使用了外部函数的变量,并且外部函数返回了内部函数,我们把这个使用外部函数变量的内部函数称为闭包。

通过闭包的定义,我们可以得知闭包的形成条件:

  1. 在函数嵌套(函数里面再定义函数)的前提下
  2. 内部函数使用了外部函数的变量(还包括外部函数的参数)
  3. 外部函数返回了内部函数

2 闭包的使用

2.1 闭包示例


# 1. 函数嵌套
def func_out():
    # 外部函数
    num1 = 10

    def func_inner(num2):
        # 内部函数
        # 2. 内部函数必须使用了外部函数的变量
        result = num1 + num2
        print("结果:", result)

    # 3. 外部函数要返回内部函数,这个使用了外部函数变量的内部函数称为闭包
    return func_inner

# 获取闭包对象
# 这个new_func就是闭包
# 这里的new_func = func_inner
new_func = func_out()
# 执行闭包
new_func(1) # 结果: 11
new_func(10) # 结果: 20

通过上面的输出结果可以看出闭包保存了外部函数内的变量 num1,每次执行闭包都是在 num1 = 10 的基础上进行计算。

2.2 修改外部变量


修改闭包内使用的外部函数变量使用 nonlocal 关键字来完成。

# 1. 函数嵌套
def func_out():
    # 外部函数的变量
    num1 = 10

    def func_inner():
        # 在闭包内修改外部函数的变量
        
        # num1 = 20  # 本意是修改外部函数变量, 其实是在闭包内定义了一个局部变量
        
        # 在闭包内修改外部函数的变量需要使用nonlocal关键字
        nonlocal num1
        num1 = 20

        # 2.内部要使用外部函数的变量
        result = num1 + 10
        print("结果:",result)
        print()

    print("修改前的外部变量:", num1)
    func_inner()
    print("修改后的外部变量:", num1)


    # 3. 返回内部函数
    return func_inner

# 创建闭包对象
new_func = func_out()
new_func()

结果为:

修改前的外部变量: 10
结果: 30

修改后的外部变量: 20
结果: 30

2.3 闭包的案例


需求: 根据配置信息使用闭包实现不同人的对话信息。

例如对话:

  • 张三: 到北京了吗?
  • 李四: 已经到了,放心吧。
# 外部函数接收姓名参数
def config_name(name):
    # 内部函数保存外部函数的参数,并且完成数据显示的组成
    def inner(msg):
        print(name + ":" + msg)

    print(id(inner)) # id不同,创建的是两个不同的闭包
    # 外部函数要返回内部函数
    return inner


# 创建tom闭包实例(对象)
tom = config_name("tom")
# 创建jerry闭包实例
jerry = config_name("jerry")
# 如果执行tom闭包,因为已经保存了name参数,那么以后在输入的时候都是,tom说:xxx
tom("哥们,过来一下,我们一起玩耍!")
jerry("打死都不去!")
tom("我不吃你")
jerry("谁相信你")

结果为:

1712302326112
1712355599552
tom:哥们,过来一下,我们一起玩耍!
jerry:打死都不去!
tom:我不吃你
jerry:谁相信你

闭包还可以提高代码的可重用性,不需要再手动定义额外的功能函数。

3 闭包的作用


闭包可以保存外部函数内的变量,不会随着外部函数调用完而销毁。

注意:

  • 由于闭包引用了外部函数的变量,外部函数的变量没有及时释放,消耗内存。

4 装饰器的定义


就是给已有函数增加额外功能的函数,它本质上就是一个闭包函数。

装饰器的功能特点:

  1. 不修改已有函数的源代码
  2. 不修改已有函数的调用方式
  3. 给已有函数增加额外的功能

5 装饰器的使用

5.1 示例代码


我们知道装饰器本质上就是一个闭包,如果 闭包函数的参数有且只有一个并且是函数类型,那么这个闭包函数称为装饰器。

# 定义装饰器
def decorator(func):  # 函数的参数有且仅有一个并且是函数类型
    print("装饰器执行了")
    def inner():
        # 在内部函数里面对已有函数进行装饰
        print("已添加登陆验证")
        func()
    return inner

# 已有函数
def comment():
    print("发表评论")

# 调用函数,decorator(comment)其实就是inner,又因为“不修改已有函数的调用方式”,所以仍然将其赋值为comment
comment= decorator(comment) 
comment()

输出:

装饰器执行了
已添加登陆验证
发表评论

注意:

  • 闭包函数有且只有一个参数,必须是函数类型,这样定义的函数才是装饰器。
  • 写代码要遵循开放封闭原则,它规定已经实现的功能代码不允许被修改,但可以被扩展。

装饰器的语法糖写法

如果有多个函数都需要添加登录验证的功能,每次都需要编写 func = decorator(func) 这样代码对已有函数进行装饰,这种做法还是比较麻烦。

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

# 定义装饰器
def decorator(func):  # 函数的参数有且仅有一个并且是函数类型
    print("装饰器执行了")
    def inner():
        # 在内部函数里面对已有函数进行装饰
        print("已添加登陆验证")
        func()
    return inner

# 已有函数
@decorator
def comment():
    print("发表评论")
comment()

输出:

装饰器执行了
已添加登陆验证
发表评论
  • @decorator 等价于 comment = decorator(comment)
  • 装饰器的执行时机: 当当前模块(当前py文件)加载完成以后,装饰器会立即执行,对已有函数进行装饰

5.2 装饰器案例


装饰器实现已有函数执行时间的统计

import time


# 定义装饰器
def decorator(func):
    def inner():
        # 内部函数对已有函数进行装饰
        # 获取时间距离1970-1-1:0:0:1的时间差
        begin = time.time()
        func()
        end = time.time()

        result = end - begin
        print("函数执行完成耗时:", result)

    return inner


@decorator  # work = decorator(work), work = inner
def work():
    for i in range(10000):
        print(i)
        
work()

6 通用装饰器的使用


通用的装饰器:可以装饰任意类型的函数。

6.1 装饰带有参数的函数


# 定义装饰器
def decorator(func):
    def inner(a, b):
        # 在内部函数对已有函数进行装饰
        print("正在努力执行加法计算")
        func(a, b)
    return inner

# 用装饰器语法糖方式装饰带有参数的函数
@decorator  #  add_num= decorator(add_num), add_num=inner
def add_num(num1, num2):
    result = num1 + num2
    print("结果为:", result)

add_num(1, 2)

输出:

--正在努力计算--
3

注意:

  • 使用装饰器装饰已有函数的时候,内部函数的类型和要装饰的已有函数的类型(参数、返回值)保持一致。

6.2 装饰带有返回值的函数


# 定义装饰器
def decorator(func):
    def inner(a, b):
        # 在内部函数对已有函数进行装饰
        print("正在努力执行加法计算")
        num =  func(a, b)
        return num

    return inner


# 用装饰器语法糖方式装饰带有参数的函数
@decorator  #  add_num= decorator(add_num), add_num=inner
def add_num(num1, num2):
    result = num1 + num2
    return result

result = add_num(1, 2)
print("结果为:", result)

输出:

正在努力执行加法计算
结果为: 3

因为内部函数的类型和要装饰的已有函数的类型保持一致,当要装饰的已有函数有返回值,内部函数也需要有返回值。

6.3 装饰带有不定长参数的函数


# 装饰器
def decorator(func):
    def inner(*args, **kwargs):
        print("--正在努力计算--")
        # *args:把元组里面的每一个元素按照位置参数的方式进行传参
        # **kwargs:把字典里面的每一个键值对按照关键字参数的方式进行传参
        num = func(*args, **kwargs)
		return num
    return inner


# 使用语法糖装饰函数
@decorator
def sum_num(*args, **kwargs):
    result = 0
	
	# args:元组类型
	# kwargs:字典类型
    for value in args:
        result += value

    for value in kwargs.values():
        result += value

    return result

result=sum_num(1, 2, a=10) 
print("结果为:",result)

输出:

--正在努力计算--
结果为: 13

6.4 通用装饰器


# 通用装饰器
def decorator(fn):
  def inner(*args, **kwargs):
      print("--正在努力计算--")
      result = fn(*args, **kwargs)
      return result

  return inner

7 多个装饰器的使用


def make_div(func):
    print("make_div装饰器执行了")

    def inner():
        # 在内部函数对已有函数进行装饰
        result = "<div>" + func() + "</div>"
        return result

    return inner

# 定义装饰器
def make_p(func):
    print("make_p装饰器执行了")

    def inner():
        # 在内部函数对已有函数进行装饰
        result = "<p>" + func() + "</p>"
        return result

    return inner

# 多个装饰器的装饰过程: 由内到外的一个装饰过程,先执行内部的装饰器,在执行外部的装饰器
# 原理剖析: content = make_div(make_p(content))
# 分步拆解: content = make_p(content),内部装饰器装完成content=make_p.inner
#  content = make_div(make_p.inner)
@make_div
@make_p
def content():
    return "人生苦短,我用python!"

result = content()
print(result)

结果为:

make_p装饰器执行了
make_div装饰器执行了
<div><p>人生苦短,我用python!</p></div>

多个装饰器的装饰过程是:离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰,由内到外的装饰过程

8 含参装饰器的使用


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

def return_decorator(flag):
    # 装饰器, 装饰器只能接收一个参数并且是函数类型
    def decorator(func):
        def inner(a, b):
            if flag == "+":
                print("正在努力执行加法计算")
            elif flag == "-":
                print("正在努力执行减法计算")
            func(a, b)
        return inner
    # 当调用函数的时候可以返回一个装饰器decorator
    return decorator


# 加法计算

# decorator = return_decorator("+"), @decorator => add=decorator(add)
@return_decorator("+")
def add(a, b):
    print(a + b)
add(1, 2)



# decorator = return_decorator("+")
# @decorator
# def add(a, b):
#     print(a + b)
# add(1, 2)

输出结果:

正在努力执行加法计算
3

使用带有参数的装饰器,其实是在装饰器外面又包裹了一个函数,使用该函数接收参数,返回是装饰器,因为 @ 符号需要配合装饰器实例使用

9 类装饰器的使用


类装饰器,就是通过定义一个类来装饰函数。

class MyDecorator(object):
    def __init__(self, func):
        self.__func = func

    # 实现__call__这个方法,让对象变成可调用的对象,可调用的对象能够像函数使用
    def __call__(self, *args, **kwargs):
        # 对已有函数进行封装
        print("课已讲完")
        self.__func()


@MyDecorator  # @MyDecorator => show = MyDecorator(show) 
# MyDecorator是一个类,show = MyDecorator(show) 相当于创建MyDecorator对象,创建对象时show这个参数没法接受,这时就需要__init__参数
def show():
    print("快要下学啦")

# 执行show  # 执行MyDecorator类创建实例对象 -> show() => 对象()
show()

# class AAA(object):
#     pass
#
# a = AAA()
# a() # 对象是不可调用

注意:

  • @MyDecorator 等价于 show = MyDecorator(show), 所以需要提供一个init 方法,并多增加一个func参数。
  • 要想类的实例对象能够像函数一样调用,需要在类里面使用call方法,把类的实例变成可调用对象(callable),也就是说可以像调用函数一样进行调用。
  • call方法里进行对 func 函数的装饰,可以添加额外的功能。

拓展:

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值