Python闭包和装饰器

⼀ 、闭包

1、作⽤域

在Python代码中,作⽤域分为两种情况:全局作⽤域 与 局部作⽤域

2、变量的作⽤域

在全局定义的变量 => 全局变量 在局部定义的变量 => 局部变量

3、全局变量与局部变量的访问范围

① 在全局作⽤域中可以访问全局变量,在局部作⽤域中可以访问局部变量

② 在局部作⽤域中可以访问全局变量

③ 在全局作⽤域中不能访问局部变量

4、问题:为什么在全局作⽤域中⽆法访问局部变量

主要原因在于,在Python的底层存在⼀个“ 垃圾回收机制” ,主要的作⽤就是回收内存空间。加快计 算机的运⾏。我们在Python代码中定义的变量也是需要占⽤内存的,所以Python为了回收已经被已经过 的内存,会⾃动将函数运⾏以后的内部变量和程序直接回收。

5、问题:我们有没有办法把函数内部的局部变量保留

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

前提:函数有嵌套
条件:
    1、内部函数使用了外部函数的变量
    2、外部函数返回了内部函数

闭包的目的:是为了保留函数内部的变量,不让它随着函数的运行结束而消失.

# 举例
def outer_fun():
    # 定义一个外部函数的局部变量
    score = 100
    def inner_fun():# 把inner_fun()称之为闭包
        # 条件1:在内部函数中使用外部函数定义的局部变量
        avg = score *10/2
        print(f"平均成绩是:{avg}")
    # 条件2:返回内部函数(在外部函数的函数体里)
    return  inner_fun

6、闭包的构成条件(三步⾛)

第⼀步:有嵌套

第⼆步:有引⽤

第三步:有返回(return)

def func():
    num = 20  # 局部变量
    def inner():
     print(num)
    return inner  # 实际inner函数并没有执⾏,只是返回了inner函数在内存中的地址
f = func()  # 相当于把inner在内存中的地址0x7fbc9b3f8e18赋值给变量f
print(id(f))  # 0x7fbc9b3f8e18
f()  # 找到inner函数的内存地址,并执⾏器内部的代码(num=20),在于闭包函数保留了num=20这个局部变

闭包的作⽤:正常情况下,当执⾏func()的时候,函数内部的变量num = 20,会随着函数的func函数的 结束⽽被垃圾回收机制所回收。

所以闭包的真正作⽤:就是可以在全局作⽤域中,实现间接对局部变量 进⾏访问。

7、注意事项

注意点: 由于闭包引⽤了外部函数的变量,则外部函数的变量没有及时释放,消耗内存。

8、在闭包的内部实现对外部变量的修改

示例一:直接修改的话是无法成功的

def outer():
 count = 0  # outer函数的局部变量
 def inner():
  count = 10  # 直接修改outer函数中的count变量
 print('outer函数中的num:', count)  # 0
 inner()  #执⾏函数inner,让num=10⽣效
 print('outer函数中的num:', count)  # 0
 return inner
counter = outer()
print(counter())  #输出None

示例二:使⽤ nonlocal关键字(在函数内部修改函数外部的变量,这个变量⾮全局变量)

def outer():
 count = 0  # outer函数的局部变量
 def inner():
  nonlocal count #声明count不是局部变量,也不是全局变量,⽽是外部函数outer的局部变量
  count = 10  # 直接修改outer函数中的count变量
 print('outer函数中的num:', count)  # 0
 inner()  #执⾏函数inner,让num=10⽣效
 print('outer函数中的num:', count)  # 0
 return inner
counter = outer()
print(counter())  #输出None

 

在 inner 函数中使⽤ nonlocal 关键字,会修改 outer 函数作⽤域内的 count 变量。因此,第⼆次 打印 outer 函数中的 count 时,它将输出10。  

示例三:使⽤ global关键字(在函数内部声明变量,代表引⽤全局作⽤域中的全局变量)

def outer():
 count = 0  # outer函数的局部变量
 def inner():
  global count
  # nonlocal count #声明count不是局部变量,也不是全局变量,⽽是外部函数outer的局部变量
  count = 10  # 直接修改outer函数中的count变量
 print('outer函数中的num:', count)  # 0
 inner()  #执⾏函数inner,让num=10⽣效
 print('outer函数中的num:', count)  # 0
 return inner
counter = outer()
print(counter())  #输出None

global 关键字是⽤来在函数内部声明⼀个变量是全局变量,⽽不是局部变量。

当你在 inner 函数中 使⽤ global count 时,你实际上是在声明⼀个全局变量 count ,并将其设置为10。

然⽽,这并不会 影响到 outer 函数中的局部变量 count 。

总结:
global使用场景:
    当在局部作用域中 修改 全局变量时,通过global关键字来说明
nonlocal使用场景:
    在函数嵌套的场景下,想在 内部函数的作用域中修改外部函数作用域中的变量时

闭包的作用:在全局作用域中间接使用了局部变量(因为它能保留函数内部的变量)
在学习闭包时,有可能会出现内部函数的局部变量修改外部函数的局部变量的情况
因此使用nonlocal能解决该问题

二、装饰器

1、什么是装饰器

在不改变现有函数源代码以及函数调⽤⽅式的前提下,实现给函数增加额外的功能。

装饰器的本质就是⼀个闭包函数(三步:① 有嵌套 ② 有引⽤ ③ 有返回)

有返回代表外部函数返回内部函数的内存地址(内部函数的名称)

2个前提:
    不改变函数的源代码
    不改变函数的调用方式
对函数的原功能做了加强或者补充

2、装饰器的雏形

装饰器:本质是⼀个闭包,有嵌套、有引⽤、有返回(返回的是函数的内存地址)

# 要求:把登录功能封装起来(⽐如封装成⼀个函数,添加这个登录不能影响现有功能函数)
def check(fn):
    def inner():
        # 开发登录功能
        print('登录功能')
        #调⽤原函数
        fn()  #调⽤comment()
    return inner
#评论功能(前提:登录)
def comment():
    print('评论功能')
comment = check(comment)  # 调⽤check(),把comment函数的内存地址赋值给comment
comment()  # 调⽤inner函数
#下载功能(前提:登录)
def download():
    print('下载功能')
download = check(download)# 调用check()。把comment函数的内存地址赋值给download
download()# 调用inner函数

参数 fn 在 check 中也是⼀个局部变量

参数 fn :就是要装饰的函数的函数名,如 comment ,如 download

3、装饰器定义

'''
装饰器:本质就是⼀个闭包①有嵌套②有引⽤③有返回
'''
def check(fn):
    def inner():
        # 开发登录验证功能
        print('验证登录')
        # 执⾏原有函数
        fn()
    return inner
@check
def comment():
    print('发表评论')
comment()

装饰器代码的执⾏流程:

1. check 是⼀个装饰器函数,它接收⼀个函数 fn 作为参数。

2. 在 check 内部定义了⼀个名为 inner 的嵌套函数,它⾸先执⾏登录验证功能,然后调⽤传⼊的原 始函数 fn 。

3. 使⽤ @check 语法糖将装饰器应⽤于comment 函数。

4. 当调⽤ comment() 时,实际上是调⽤了装饰器 check 内部的inner 函数。

5. inner 函数先打印’ 验证登录’ ,然后调⽤原始的 comment 函数打印’ 发表评论’ 。

4、装饰器的作⽤:获取程序的执⾏时间

'''
定义获取程序的执⾏时间装饰器=> 闭包(①有嵌套②有引⽤③有返回)
'''
import time
def get_time(fn):
    def inner():
        # ①添加装饰器修饰功能(获取程序的执⾏时间)
        begin = time.time()
        # ②调⽤fn函数,执⾏原函数代码
        fn()
        end = time.time()
        print(f'这个函数的执⾏时间:{end - begin}')
    return inner
@get_time
def demo():
    for i in range(1000000):
        print(i)
demo()

5、带有参数装饰器

'''
    带有参数的装饰器:①有嵌套②有引⽤③有返回
'''
def logging(fn):
    def inner(*args, **kwargs):
        # 添加装饰器代码(输出⽇志信息)
        print('--⽇志信息:正在努⼒计算机- -')
        # 执⾏要修饰的函数
        fn(*args, **kwargs)  # sum_num(a, b)
    return inner
@logging
def sum_num(*args, **kwargs):
    result = 0
    # *args代表不定⻓元组参数,args = (10, 20)
    for i in args:  # i = 10, i = 20
        result += i
    # **kwargs代表不定⻓字典参数, kwargs = {a:30, b: 40}
    for i in kwargs.values():  # i = 30, i = 40
        result += i
    print(result)
# sum_num带4个参数,⽽且类型不同,10和20以元组形式传递,a = 30,b = 40以字典形式传递
sum_num(10, 20, a=30, b=40)

6、带有返回装饰器

'''
    带有返回值的装饰器:①有嵌套②有引⽤③有返回如果⼀个函数执⾏完毕后,没有return返回值,则默认返回None
'''
def logging(fn):
    def inner(*args, **kwargs):
        print('--⽇志信息:正在努⼒计算  - -')
        return fn(*args, **kwargs)  # fn() = sub_num(20, 10) = result
    return inner
@logging
def sub_num(a, b):
    result = a - b
    return result
print(sub_num(20, 10))

7、通⽤版本的装饰器(以后所有的装饰器以此为准)

所谓的通用装饰器
闭包的基础上,
加参数:1、外部函数加上一个  函数 参数;2、内部函数加上不定长参数加返回值

'''
    通⽤装饰器:①有嵌套②有引⽤③有返回④有不定⻓参数⑤有return返回值
'''
def logging(fn):
    def inner(*args, **kwargs):
        # 输出装饰器功能
        print('--正在努⼒计算 - -')
        # 调⽤fn函数
        return fn(*args, **kwargs)
    return inner
@logging
def sum_num1(a, b):
    result = a + b
    return result
print(sum_num1(20, 10))
@logging
def sum_num2(a, b, c):
    result = a + b + c
    return result
print(sum_num2(10, 20, 30))

8、装饰器⾼级:使⽤装饰器传递参数(了解)

基本语法:

def 装饰器(fn):
    ...
@装饰器('参数')
def 函数():
    #函数代码

示例:

'''
    通⽤装饰器:①有嵌套②有引⽤③有返回④有不定⻓参数⑤有return返回值
    真正问题:通过装饰器传递参数,我们应该如何接收这个参数呢?
    答:在logging⽅法的外侧在添加⼀个函数,专⻔⽤于接收传递过来的参数
'''
def logging(flag):
    # flag = + 或flag = -
    def decorator(fn):
        def inner(*args, **kwargs):
            if flag == '+':
                print('--⽇志信息:正在努⼒进⾏加法运算- -')
            elif flag == '-':
                print('--⽇志信息:正在努⼒进⾏减法运算 - -')
            return fn(*args, **kwargs)
        return inner
    return decorator

@logging('+')
def sum_num(a, b):
    result = a + b
    return result

@logging(' - ')
def sub_num(a, b):
    result = a - b
    return result
print(sum_num(10, 20))
print(sub_num(100, 80))

执⾏过程和解释:

1. 定义 logging 函数:

○ logging 函数是⼀个装饰器⼯⼚,它接收⼀个参数 flag ,这个参数⽤于确定要执⾏的运算 类型(加法或减法)。

○ logging 函数内部定义了⼀个装饰器

2. 定义 decorator 装饰器:

○ decorator ,它接收⼀个函数 fn 作为参数。 decorator 是⼀个真正的装饰器,它接收⼀个函数 fn ,并返回⼀个内部函数 inner 。

○ inner 函数接收不定⻓参数 *args 和关键字参数 传递给原始函数 fn 。 **kwargs ,这使得它可以接收任何参数并 14

3. 在 inner 函数内部:

○ 根据 flag 参数的值,打印出相应的⽇志信息。

○调⽤原始函数 fn ,并传递inner 接收到的所有参数。

○返回原始函数的返回值。

4. 使⽤ @logging('+') 和 @logging('-') 装饰器:

○ 使⽤ @logging('+') 装饰 sum_num 函数,将 递 '+' 作为参数。

○使⽤ @logging('-') 装饰 logging 装饰器应⽤于该函数,并传 sub_num 函数,将 logging 装饰器应⽤于该函数,并传递 ' 作为参数。

5. 定义 sum_num 和 sub_num 函数:

○ sum_num 函数接收两个参数,计算它们的和,并返回结果。

○ sub_num 函数接收两个参数,计算它们的差,并返回结果。

6. 调⽤ sum_num 和 sub_num 函数:

○ 调⽤ sum_num(10, 20) ,装饰器会打印加法⽇志信息,并返回30。

○调⽤ sub_num(100, 80) ,装饰器会打印减法⽇志信息,并返回20。

9、应用举例

现在项目小组的组长,给了你一个任务,让你设计一个通用性的装饰器供整个开发小组的人使用。
问题:
    小组一共有3个人,
    小张负责写登录功能函数
    小李负责写评论功能函数
    小刘负责写喜欢功能函数
    假设,小组3人都需要统计自己所写函数运行消耗的时间除此以外,
    小张还需要在执行登录功能之前,记录登录者的ip和时间
    小李还需要在评论功能之前,要验证是否登录
    小刘还需要在喜欢功能之前,验证是否已关注
分析:
    1、先定义出张、李、刘3人原始的函数
    2、定义一个装饰器,去统计函数花费的时间(大家都共用的装饰器)
    3、再定义一个装饰器,去满足个人不同业务场景的需求(个人特色)
思路:
    1、先写的3人的原始函数
    2、调用3人的原始函数(此时没有任何增强的效果)
    3、写3人都具备的通用增强功能,即对原始函数执行所消耗的时间。时间消耗统计的装饰器
    4、不同人员各自的特色增强函数,通过传递一个函数作为装饰器内部函数的参数来进行关联
    5、编写各自特色的增强函数
    6、调用时增加特色函数名作为参数传递给装饰器
"""
    装饰器的本质是闭包,它的作用是为了给   原始函数   进行功能增强
"""
def outer_fun(fun):
    def inner_fun(your_doing_fun,*args,**kwargs):
        # print("写装饰代码-----在原始函数运行之前")

        # 运行调用者想要执行的个性行为函数.
        # 张 记录登录者的ip + 登录时间
        # 李 验证登录
        # 刘 检验是否已经关注
        your_doing_fun()

        before = time.time()# 记录原始函数运行前的时间
        run_res = fun(*args,**kwargs) # 运行原始函数
        after = time.time()#记录原始函数运行之后的时间
        print(f"函数运行消耗了:{after - before}秒")
        # print("写装饰代码-----在原始函数运行之后")
        return run_res
    return inner_fun
@outer_fun
def zhang():
    print("张   的原始  登录函数     已执行.........")
@outer_fun
def li():
    print("李   的原始  评论函数     已执行。。。。。。")
@outer_fun
def liu():
    print("刘   的原始  喜欢函数     已执行----------")

# 定义自己传入进去的特色功能函数
def zhang_self():
    print("张自己的个人其它增强功能-------记录登录者的ip + 登录时间")
def li_self():
    print("李 验证登录----------------")
def liu_self():
    print("刘 检验是否已经关注==========")

zhang(zhang_self)
li(li_self)
liu(liu_self)


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值