17.Python基础篇-闭包、装饰器

函数的进阶—闭包

闭包的定义:嵌套函数,内部函数调用外部函数的变量。满足这个条件就算闭包。

闭包案例演示:

def outer():
    a = 1
    def inner():
        print('inner函数中打印的变量a:',a)  # 嵌套函数中使用了外层函数的变量。此时满足了闭包的条件。
    return inner  # 常用的方式,将inner的内存地址作为返回值,可以在外部直接调用inner,并且inner可以使用外层的变量:a。从而延长了变量a的生命周期

inn = outer()
print('打印inn', inn)  # 打印结果为inner函数的内存地址
inn()  # 内存地址+(),即表示调用inner这个函数

输出内容:
打印inn <function outer.<locals>.inner at 0x0000028217752670>
inner函数中打印的变量a: 1

 

 函数的进阶-装饰器

装饰器的作用

主要作用是在不修改原始函数的情况下,为其增加额外的功能。应用于日志功能:记录函数的调用;性能测试:统计函数执行的时间等...

装饰器与闭包的关系

装饰器是闭包的应用,装饰器一定是一个闭包函数,但是闭包并不一定是个装饰器。

逐步完善-装饰器的写法

1.0版本-不使用语法糖的写法(为了便于理解)

# 装饰器的固定写法(不使用语法糖得版本,为了便于理解)

# 定义一个装饰器函数
def wrapper(func):  # 接收被装饰函数的函数名(内存地址)
    def inner(*args, **kwargs):  # 这俩可变参数可以兼容所有参数类型的函数
        print("在被装饰函数之前要做的事情")
        ret = func(*args, **kwargs)  # 调用被装饰函数,并接收返回值
        print("在被装饰函数之后要做的事情")
        return ret  # 将被装饰函数的返回值,返回给调用者
    return inner

# 被装饰的函数
def func(a, b):
    print("被装饰的函数")
    print("打印两个参数:", a, b)


func = wrapper(func)
func(1, 2)  # 调用func演示

2.0版本-使用语法糖的写法

# 装饰器的固定写法(使用语法糖)

# 定义一个装饰器函数
def wrapper(func):  # 接收被装饰函数的函数名(内存地址)
    def inner(*args, **kwargs):  # 这俩可变参数可以兼容所有参数类型的函数
        print("在被装饰函数之前要做的事情")
        ret = func(*args, **kwargs)  # 调用被装饰函数,并接收返回值
        print("在被装饰函数之后要做的事情")
        return ret  # 将被装饰函数的返回值,返回给调用者
    return inner

# 被装饰的函数
@wrapper  # @wrapper 等价于 func = wrapper(func) 。使用@wrapper语法糖把函数加上装饰器
def func(a, b):
    print("被装饰的函数")
    print("打印两个参数:", a, b)


func(1, 2)  # 调用func演示

语法糖是什么:本质上,语法糖是对已有语法结构的封装或简化,让开发者用更简洁的方式表达复杂的逻辑。(引用于ChatGPT)

在python中有些功能使用了语法糖特性?

  1. Lambda 表达式
  2. 装饰器
  3. 三元表达式
  4. 解包赋值

3.0版本-使用@wraps装饰器,解决被装饰函数无法获取真实的函数名问题

背景:使用函数名.__name__可以获取函数的函数名。但是如果按照前面的写法,将无法获取被装饰函数的真实函数名,实际拿到的是装饰器函数的方法名。代码说明:

def func1_name():
    pass

print(func1_name.__name__)  # 打印出func1_name函数的函数名

输出结果:
func1_name

 

# 定义一个装饰器函数
def wrapper(func):
    def inner(*args, **kwargs):
        print("在被装饰函数之前要做的事情")
        ret = func(*args, **kwargs)
        print("在被装饰函数之后要做的事情")
        return ret
    return inner

# 被装饰的函数
@wrapper
def func(a, b):
    print("被装饰的函数")
    print("打印两个参数:", a, b)

print(func.__name__)

输出结果:
inner

# func是被装饰的函数,按理说我们的需求打印出的应该是"func",也就是函数本身的方法名。现在的执行结果却是"inner"(装饰器内部嵌套函数的方法名)。
# 为了解决获取不到被装饰函数的真实函数名问题,则需要使用functools中的@wraps装饰器,装饰在装饰器的内部方法上
from functools import wraps

def wrapper(func):
    @wraps(func)   # 装饰器内部函数增加@wraps方法装饰后,就支持查看被装饰函数真实的函数名
    def inner(*args, **kwargs):
        print("在被装饰函数之前要做的事情")
        ret = func(*args, **kwargs)
        print("在被装饰函数之后要做的事情")
        return ret
    return inner

# 被装饰的函数
@wrapper
def func():
    print("被装饰的函数")


print(func.__name__)  # 打印被装饰函数的函数名

输出结果:
func

4.0版本-三层嵌套的装饰器,支持装饰器传参,可以用传入标志位来控制全局的装饰器是否生效

 案例演示:

import time

FLAGE = True  # 标志位。True:使用装饰器,False:不使用装饰器

def timmer_out(flage):  # 接收参数FLAGE
    def warpper(func):
        def inner(*args, **kwargs):
            if flage:  # FLAGE为True执行装饰代码
                start_time = time.time()
                ret = func(*args, **kwargs)
                end_time = time.time()
                print(end_time-start_time)
                return ret
            else:  
                ret = func(*args, **kwargs)
                return ret
        return inner
    return warpper


# 此处为两部分:
# 第一部分:@
# 第二部分:timmer_out(FLAGE),一个普通的方法调用。
# 1、程序先执行第二部分timmer_out(FLAGE),此处为一个普通的方法调用timmer_out(),timmer_out返回值为warpper的内存地址。
# 2、再执行@,@后的内容为调用timmer_out方法的返回值,返回值为warpper的内存地址,则此处相当于@warpper。
# 通过上面两步,实现了标志位传参,且不影响装饰器功能的正常使用。进而实现通过简单的方式来控制装饰器开关。
 
# @timmer_out(FLAGE) 这句代码等价于 func1 = timmer_out(FLAGE)
@timmer_out(FLAGE)
def func1():
    time.sleep(1)
    print("func1")

@timmer_out(FLAGE)
def func2():
    time.sleep(0.1)
    print("func2")

func1()
func2()

# 输出结果:
# func1
# 1.0057849884033203
# func2
# 0.10775208473205566

 

 一个函数使用多个装饰器的情况下,代码执行顺序说明:

import time



def warpper1(func):
    def inner(*args, **kwargs):
        print('*****warpper1--befor')
        ret = func(*args, **kwargs)
        print('*****warpper1--after')
        return ret
    return inner

def warpper2(func):
    def inner(*args, **kwargs):
        print('*****warpper2--befor')
        ret = func(*args, **kwargs)
        print('*****warpper2--after')
        return ret
    return inner

@warpper1
@warpper2
def func():
    print('###func###')

func()

# 执行结果:
*****warpper1--befor
*****warpper2--befor
###func###
*****warpper2--after
*****warpper1--after

 

画图说明:

 

 4.0版本-完整的装饰器,集合了上面3个版本的各个功能

from functools import wraps

FLAGE = True  # 标志位。True:使用装饰器,False:不使用装饰器


# 定义一个装饰器函数
def wrapper_out_1(flage):
    def wrapper1(func):  # 接收被装饰函数的函数名(内存地址)
        @wraps(func)  # 装饰器内部函数增加@wraps方法装饰后,就支持查看被装饰函数真实的函数名
        def inner1(*args, **kwargs):  # 这俩可变参数可以兼容所有参数类型的函数
            if flage:  # FLAGE为True执行装饰代码
                print("在被装饰函数之前要做的事情", wrapper_out_1.__name__)
                ret = func(*args, **kwargs)  # 调用被装饰函数,并接收返回值
                print("在被装饰函数之后要做的事情", wrapper_out_1.__name__)
                return ret  # 将被装饰函数的返回值,返回给调用者
            else:  # FLAGE为false则不执行装饰代码
                ret = func(*args, **kwargs)  # 调用被装饰函数,并接收返回值
                return ret  # 将被装饰函数的返回值,返回给调用者
        return inner1
    return wrapper1


# 与wrapper1代码一样
def wrapper_out_2(flage):
    def wrapper2(func):
        @wraps(func)
        def inner2(*args, **kwargs):
            if flage:
                print("在被装饰函数之前要做的事情", wrapper_out_2.__name__)
                ret = func(*args, **kwargs)
                print("在被装饰函数之后要做的事情", wrapper_out_2.__name__)
                return ret
            else:
                ret = func(*args, **kwargs)
                return ret
        return inner2
    return wrapper2

# 被装饰的函数
@wrapper_out_1(FLAGE)
@wrapper_out_2(FLAGE)  # @wrapper_out_2(FLAGE) 这句代码等价于 func = wrapper_out_2(FLAGE)
def func(a, b):
    print("被装饰的函数")
    print("打印两个参数:", a, b)

func(1, 2)  # 调用func演示

# 执行结果:
在被装饰函数之前要做的事情 wrapper_out_1
在被装饰函数之前要做的事情 wrapper_out_2
被装饰的函数
打印两个参数: 1 2
在被装饰函数之后要做的事情 wrapper_out_2
在被装饰函数之后要做的事情 wrapper_out_1

 

posted @ 2024-10-17 17:22  邵杠杠  阅读(19)  评论(0编辑  收藏  举报