闭包函数与装饰器

闭包函数

什么是闭函数?

闭就是封闭的意思,函数被封闭起来,f2就是闭函数,f2只能在f1的局部访问

def f1():
    def f2():
        pass

什么是包函数?

函数内部包含对外层函数作用域名字的引用,不包含全局

def f1():
    x = 10
    def f2():
        print(x)

f2被封闭在f1内部了,要调用它只能在f1内部调用,全局是没办法访问到f2的。

需求:全局访问f2

解决思路:函数可传递、可以当做返回值被另一个函数返回,把局部函数的内存地址拿到全局来用

def f1():
    x = 10
    def f2():
        print(x)

    return f2

# res 为f2的内存地址
res = f1()
print(res)
x = 20  
res() # 输出结果为10

在全局修改x=20,f1打印的还是10,因为名字的查找顺序是在定义阶段就决定的,和我们在哪里调用时没有关系的,找x时,找的是包里的x。要做到x可修改,将x作为f1的入参。

def f1(x):
    def f2():
        print(x)
    return f2

# res 为f2的内存地址
res = f1(10)
res()

什么时候用到闭包函数?

f2不能够增加新的形参,而f2又需要外部传参

应用场景:装饰器

装饰器

器:工具,造工具使用函数就可以

装饰:为其他事物添加额外功能

装饰器:定义一个函数/类,这个函数/类的功能就是用来给其他函数添加额外功能的

给函数增加功能还需要单独写一个函数吗?

开放封闭原则

面向对象的核心

开发:对拓展功能(增加功能)开放,在源代码不做任何修改的情况下,为其增加新功能

封闭:对修改源代码是封闭的

增加新的功能,增加新的功能,增加新的功能

不要去动源代码,写一个新函数给它增加新功能

示例

eg:

import time
def inside(group, s):
    print('欢迎来到英雄联盟')
    print(f'你出生在{group}方阵营')
    print(f'敌军还有{s}秒到达战场')
    time.sleep(s)
    print('全军出击')

inside('蓝色', 3)

在此函数基础上增加统计该函数执行时间

方案一:没有修改调用方式,修改了源代码,违反开放封闭原则

import time

def inside(group, s):
    start = time.time()
    print('欢迎来到英雄联盟')
    print(f'你出生在{group}方阵营')
    print(f'敌军还有{s}秒到达战场')
    time.sleep(s)
    print('全军出击')
    end = time.time()
    print(end-start)

inside('蓝色', 3)

方案二:没有修改源码和调用方式,但加入inside多次调用,存在大量重复代码,low

import time

def inside(group, s):

    print('欢迎来到英雄联盟')
    print(f'你出生在{group}方阵营')
    print(f'敌军还有{s}秒到达战场')
    time.sleep(s)
    print('全军出击')

start = time.time()
inside('蓝色', 3)
end = time.time()
print(end - start)

start = time.time()
inside('蓝色', 3)
end = time.time()
print(end - start)

start = time.time()
inside('蓝色', 3)
end = time.time()
print(end - start)

方案三:解决方案二代码冗余问题,没有修改源代码,增加了新功能,但是改变了调用方式,而且wrapper函数inside调用传参写死了

import time

def inside(group, s):

    print('欢迎来到英雄联盟')
    print(f'你出生在{group}方阵营')
    print(f'敌军还有{s}秒到达战场')
    time.sleep(s)
    print('全军出击')

def wrapper():
    start = time.time()
    inside('蓝色', 3)
    end = time.time()
    print(end - start)

wrapper()

方案四:为了解决参数写死问题,使用*args **kwargs解决

import time

def inside(group, s):

    print('欢迎来到英雄联盟')
    print(f'你出生在{group}方阵营')
    print(f'敌军还有{s}秒到达战场')
    time.sleep(s)
    print('全军出击')

def wrapper(*args, **kwargs):
    start = time.time()
    inside(*args, **kwargs)
    end = time.time()
    print(end - start)

wrapper('蓝色', 3)

方案五:假如另外一个函数也需要统计执行时间,wrapper没法直接复用

传参两种方式

直接传:因为传入的参数时直接给被修饰对象的,不能直接传

闭包函数:将修饰的地址返回重新复制,对于调用者来说不感知

import time

def inside(group, s):

    print('欢迎来到英雄联盟')
    print(f'你出生在{group}方阵营')
    print(f'敌军还有{s}秒到达战场')
    time.sleep(s)
    print('全军出击')

def outer(func):
    # func = inside
    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, **kwargs)
        end = time.time()
        print(end - start)
    return wrapper

# func 指向 inside
# inside 指向 outer(inside)
inside = outer(inside)


# 对于调用者来说,不知道被修饰了
inside('蓝色', 3)

方案六 加入原函数有返回值,使用上面的装饰器,返回值会丢失

import time

def recharge(num):
    for i in range(num, 101):
        time.sleep(0.05)
        print(f'\r当前电量:{"*"*i} {i}%', end='')
    print('充电完毕!')
    return 100


def outer(func):
    # func = inside
    def wrapper(*args, **kwargs):
        start = time.time()
        response = func(*args, **kwargs)
        end = time.time()
        print(end - start)
        return response  #增加返回值
    return wrapper

recharge = outer(recharge)
res = recharge(20)
print(res)

语法糖

用了这个语法之后,代码看起来更加简洁,写起来很爽,看起来也很爽,就像吃了糖一样。

每次装饰对象,都需要执行以下outer,把被装饰对象传进去,得到新的函数地址,再覆盖原来的名字。

有没有一种简洁的方式来完成这件事?

在需要装饰对象前增加@装饰名字

import time


def count_time(func):
    # func = inside
    def wrapper(*args, **kwargs):
        start = time.time()
        response = func(*args, **kwargs)
        end = time.time()
        print(end - start)
        return response
    return wrapper

@count_time # inside = count_time(inside)
def inside(group, s):
    print('欢迎来到英雄联盟')
    print(f'你出生在{group}方阵营')
    print(f'敌军还有{s}秒到达战场')
    time.sleep(s)
    print('全军出击')

@count_time # recharge = count_time(recharge)
def recharge(num):
    for i in range(num, 101):
        time.sleep(0.05)
        print(f'\r当前电量:{"*"*i} {i}%', end='')
    print('充电完毕!')
    return 100

recharge(20)
inside('蓝色', 3)

装饰器模板

在pycharm中设置

File->Settings->Live Templates-> Python-> +

def outer(func):
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        return res
    return wrapper

比如用装饰器实现了认证功能,那么 需要认证功能时,只需要使用装饰器即可

def outer(func):
    def wrapper(*args, **kwargs):
        name = input('请输入账号>>>').strip()
        pwd = input('请输入密码>>>').strip()
        if name == 'jack' and pwd == '123':
            res = func(*args, **kwargs)
            return res
        else:
            print('账号或密码错误!')
    return wrapper

@outer
def home():
    time.sleep(2)
    print('welcome')

home()

完美伪装

之前的装饰器其实还不够完美,因为函数地址、__name__等属性能看出是否被装饰.

怎么才能伪装的更像?

使用functools中wraps装饰器来装饰wrapper

其实最重要的伪装,参数,调用方式,返回值已经伪装的很好了,wraps只是做一些不太重要的伪装。

from functools import wraps

def outer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        name = input('请输入账号>>>').strip()
        pwd = input('请输入密码>>>').strip()
        if name == 'jack' and pwd == '123':
            res = func(*args, **kwargs)
            return res
        else:
            print('账号或密码错误!')
    return wrapper

@outer
def home():
    time.sleep(2)
    print('welcome')

# home()
print(home.__name__)
print(home.__doc__)

有参装饰器

当函数内部需要一个参数时,有两种方案可以实现:

1直接通过参数传入

2用闭包函数概念包给它

如果第一种方案可以解决,就尽量不要用第二种方案

加入wrpper需要一个新参数,怎么传入?

直接在通过outer传入,不行。因为语法糖的限制,只能传一个参数,而且是函数的地址

def outer(func, name): # 报错
    def wrapper(*args, **kwargs):
        print(name)
        res = func(*args, **kwargs)
        return res
    return wrapper

@outer
def home():
    time.sleep(2)
    print('welcome')

因此outer参数不能用动,wrapper参数不能动,使用有参装饰器。

def g_outer(name):
    def outer(func):
        def wrapper(*args, **kwargs):
            print(name)
            res = func(*args, **kwargs)
            return res
        return wrapper
    return outer

# outer = g_outer('张大仙')
# g_outer()调用返回outer地址,再和@结合构成语法糖
@g_outer('张大仙')
def home():
    time.sleep(2)
    print('welcome')

装饰器叠加

def outer1(func1):
    def wrapper(*args, **kwargs):
        print('开始执行outer1.wrapper')
        res = func1(*args, **kwargs)  # func1 = home
        print('outer1.wrapper执行完毕')
        return res
    return wrapper

def outer2(x):
    def outer(func2):
        def wrapper(*args, **kwargs):
            print('开始执行outer2.wrapper')
            res = func2(*args, **kwargs) # func2 = outer1.wrapper
            print('outer2.wrapper执行完毕')
            return res
        return wrapper
    return outer

def outer3(func3):
    def wrapper(*args, **kwargs):
        print('开始执行outer3.wrapper')
        res = func3(*args, **kwargs) # func3 = outer2.wrapper
        print('outer3.wrapper执行完毕')
        return res
    return wrapper

@outer3  # home=outer3(home) outer3.wraper
@outer2(10)  # home=outer2(home) outer2.wraper
@outer1  # home=outer1(home) outer1.wraper
def home(z):
    print('执行home功能', z)

home(0)

执行结果如下

开始执行outer3.wrapper
开始执行outer2.wrapper
开始执行outer1.wrapper
执行home功能 0
outer1.wrapper执行完毕
outer2.wrapper执行完毕
outer3.wrapper执行完毕
最后修改:2023 年 07 月 20 日
如果觉得我的文章对你有用,请随意赞赏