您现在的位置是:首页 >技术教程 >Python进阶:闭包、装饰器浅析,一对表兄弟网站首页技术教程
Python进阶:闭包、装饰器浅析,一对表兄弟
函数参数
函数名的意义
先上代码
def func_1():
    print("这是个很厉害的函数")
print(func_1) 
执行结果如下:
<function func_1 at 0x0000016E97A0B1C0> 
可以看出函数名代表的就是函数内容在内存中的地址。
函数名():如func_1(),即表示执行函数名func_1所在地址中存储的代码。
函数作为参数
def func_1():
    print("这是个很厉害的函数")
def test_func_param(func):
    func()
test_func_param(func_1) 
上诉代码将func_1作为参数传递给test_func_param,传递的为func_1函数的地址,因此方法test_func_param中的func()执行的即为func_1函数。
闭包
闭包的作用
闭包可以保存函数中的变量,使其不会随着函数的调用结束而销毁。
闭包的定义
在函数嵌套的前提下,内部函数使用了外部函数的变量,外部函数又返回了内部函数。则这个使用了外部函数变量的内部函数就叫闭包。
由上述可得闭包形成的三个条件
- 函数嵌套
 - 内部函数使用了外部函数的变量
 - 外部函数返回了内部函数
 
代码如下:
# 1.定义一个外部函数嵌套一个内部函数
def outer(num1):
    # 2.嵌套的内部函数,并且在内部函数中访问了外部函数变量
    def inner(num2):
        num = num2 + num1
        print(f"执行结果:{num}")
    # 3.外部函数返回了内部函数
    return inner
f = outer(10)
f(10) 
执行结果为:
执行结果:20 
闭包内修改外部变量
正常情况下直接修改外部变量的值是不起作用的。如下:
# 1.定义一个外部函数嵌套一个内部函数
def outer(num1):
    # 2.嵌套的内部函数,并且在内部函数中访问了外部函数变量
    def inner(num2):
        num1 = num2 + 10
    # 3.外部函数返回了内部函数,返回的函数即为闭包
    print(num1)
    inner(10)
    print(num1)
    return inner
outer(10) 
执行结果为:
10
10
这说明对于外部变量num1的修改并没有起作用,这时候nonlocal就登场了。nonlocal的使用方式极其简单,有点类似global,如下:
# 1.定义一个外部函数嵌套一个内部函数
def outer(num1):
    # 2.嵌套的内部函数,并且在内部函数中访问了外部函数变量
    def inner(num2):
        nonlocal num1
        num1 = num2 + 10
    # 3.外部函数返回了内部函数,返回的函数即为闭包
    print(num1)
    inner(10)
    print(num1)
    return inner
outer(10)
 
执行结果为:
10
20
装饰器
作用
在不改变函数源代码的情况下给函数增加相应的功能,装饰器的本质就是闭包。
原始使用
# 使用闭包实现装饰器功能
def check(fn):
    def inner():
        print("实名登录")
        fn()
    return inner
# 原始方法
def comment():
    print("你相信光嘛?")
# 使用装饰器重新装饰comment方法
comment = check(comment)
comment() 
这样既可实现在不改变comment函数源代码的情况下给comment函数增加部分功能。
语法糖写法
# 使用闭包实现装饰器功能
def check(fn):
    def inner():
        print("实名登录")
        fn()
    return inner
# 语法糖
@check
def comment():
    print("你相信光嘛?")
# 使用装饰器装饰后的方法
comment() 
两者效果一样,可以看出@check做的事情其实就是comment = check(comment),语法糖的原理是不是瞬间明了了。
以上代码能看出装饰器的本质就是将函数作为参数传递给闭包中的外部函数,同时在内部函数使用该函数,并且添加对应功能。
装饰带有参数的函数
# 使用闭包实现装饰器功能
def check(fn):
    def inner(a, b):
        print("数据校验")
        fn(a, b)
    return inner
# 语法糖
@check
def add(a, b):
    print(f"求和为:{(a + b)}")
# 使用装饰器装饰后方法
add(1, 2) 
装饰带有返回值的函数
# 使用闭包实现装饰器功能
def check(fn):
    def inner(a, b):
        print("数据校验")
        return fn(a, b)
    return inner
# 语法糖
@check
def add(a, b):
    return a + b
# 使用装饰器装饰后的方法
print(add(1, 2)) 
执行结果如下
数据校验
3
装饰不定长参数函数
# 使用闭包实现装饰器功能
def check(fn):
    def inner(*args, **kwargs):
        print("数据打印")
        fn(args, kwargs)
    return inner
# 语法糖
@check
def scan(*args, **kwargs):
    print(args, kwargs)
# 使用装饰器装饰后的方法
scan(1, 2, love = "China") 
多个装饰器装饰一个方法
# 使用闭包实现装饰器1功能
def check1(fn):
    def inner():
        print("登录")
        fn()
    return inner
# 装饰器2
def check2(fn):
    def inner():
        print("实名")
        fn()
    return inner
# 语法糖
@check1
@check2
def comment():
    print("发表评论")
# 使用装饰器装饰后的方法
comment() 
执行结果
登录
实名
发表评论
带参数的装饰器
注意这里是给装饰器加参数,上面那个是指给主要装饰的函数加参数,两者完全不同,那么怎么给装饰器加参数呐?首先明白亮点
- 装饰器的外部函数只能接受一个参数,那就是被装饰的函数,也就是之前代码中的fn参数
 - 既然不能给装饰器外部函数添加第二个参数,那么就可以考虑再外部函数之外再加一个函数也就是套娃。
 
代码如下:
def param_test(check_data):
    # 使用闭包实现装饰器功能
    def check(fn):
        def inner():
            if check_data == "true":
                print("登录")
            fn()
        return inner
    return check
# 语法糖
@param_test('true')
def comment():
    print("发表评论")
# 使用装饰器装饰后的方法
comment() 
如上,check为原本的装饰器函数,在外部在套一层函数实现将参数check_data传递给装饰器的作用,执行结果如下,毫无问题。
登录
发表评论
类装饰器
__call__方法:一个类一旦实现了__call__方法,那么这个类的实例对象就可以像方法那样调用,调用的结果即为__call__方法的执行结果,如下:
class Check():
    def __call__(self, *args, **kwargs):
        print("call的使用")
c = Check()
c() 
执行结果为:
call的使用
c即为Check类的实例,c()执行的即为__call__方法的内容。
类装饰器如下:
# 类装饰器
class Check:
    def __init__(self, fn):
        self.__fn = fn
    # call方法实现
    def __call__(self, *args, **kwargs):
        print("登录")
        self.__fn()
# 相当于 comment = Check(comment)
@Check 
def comment():
    print("评论")
comment() 
其中@Check相当于comment = Check(comment),不清楚要掉头回去看看装饰器第一段代码了,Check(comment)初始化了类并传入comment对象,执行结束后comment即为类的实例,在执行comment()则触发call方法。
            




U8W/U8W-Mini使用与常见问题解决
QT多线程的5种用法,通过使用线程解决UI主界面的耗时操作代码,防止界面卡死。...
stm32使用HAL库配置串口中断收发数据(保姆级教程)
分享几个国内免费的ChatGPT镜像网址(亲测有效)
Allegro16.6差分等长设置及走线总结