python闭包和装饰器

闭包

  • 名称空间
  • 作用域

命名空间

大家可能已经知道Python中的函数、列表、常量等都是对象,而命名空间则是存放这些对象名称与其对象本身这一映射关系的地方。

比如string=“hello word!” 我们定义了一个str类型的值,通过string这个引用就能够访问"hello world"

这种关系就像一个字典结构,Python中可以有多个独立的命名空间,这其中不同命名空间的名称是可以重复使用的,命名空间也是分层次的。

python程序执行期间会有2个或3个活动的命名空间(函数调用时有3个,函数调用结束后2个)。按照变量定义的位置,可以划分为以下3类:

Local,局部命名空间,每个函数所拥有的命名空间,记录了函数中定义的所有变量,包括函数的入参、内部定义的局部变量。

Global,全局命名空间,每个模块加载执行时创建的,记录了模块中定义的变量,包括模块中定义的函数、类、其他导入的模块、模块级的变量与常量。

Built-in,python自带的内建命名空间,任何模块均可以访问,放着内置的函数和异常。


Local(局部命名空间)在函数被调用时才被创建,但函数返回结果或抛出异常时被删除。(每一个递归函数都拥有自己的命名空间)。

Global(全局命名空间)在模块被加载时创建,通常一直保留直到python解释器退出。

Built-in(内建命名空间)在python解释器启动时创建,一直保留直到解释器退出。

各命名空间创建顺序:python解释器启动 ->创建内建命名空间 -> 加载模块 -> 创建全局命名空间 ->函数被调用 ->创建局部命名空间

各命名空间销毁顺序:函数调用结束 -> 销毁函数对应的局部命名空间 -> python虚拟机(解释器)退出 ->销毁全局命名空间 ->销毁内建命名空间

python解释器加载阶段会创建出内建命名空间、模块的全局命名空间,局部命名空间是在运行阶段函数被调用时动态创建出来的,函数调用结束动态的销毁的。

作用域

既然有的命名空间是可以重复使用

,那么该怎么确定我想要的值在哪里呢,这里就要说到作用域了

简单举个栗子
你觉得下面这个函数输出的值是多少呢

a=1
def func():
	a=5
	print(a)
func()

结果是

5

为什么呢这里就要说到Python查找名称空间顺序的规则了

  • LEGB规则
  • Local 在一个函数或类的内部。
  • Enclosed 在嵌套函数内比如闭包。
  • Global 在当前模块的全局。
  • Built-in 是Python为自身保留的特殊名称。

按照这个规则我们很容易就能判断出最终上面函数所输出的a的值

闭包函数

*简单来讲闭包函数就是在一个嵌套函数中,内部函数能够访问外部函数所有的参数、局部变量、及定义的其他内部函数,这样的函数在外层函数之外被调用就会形成闭包,也就是这个函数拎出来就像外面包裹着一层作用域。闭包可以看做一种传参方式
标准模板

def outer():
    def inner():
        pass
    return inner  # 注意这里返回的是inner 而不是inner()  
    				inner 内函数的地址 inner()是值

例子

def outer():
    a=100
    def inner():
        print(a)
    return inner 

a=5   # 在全局给个a=5
res=outer()   
res()      
# 结果       
100                    
#这里就能很直观地理解什么叫闭包,一层作用域 包裹着这个内层函数
#无论在哪里调用这个闭包函数,a的值永远只受outer内的那个a影响

装饰器

装饰器是在不修改原函数源码及调用方式的情况下给函数扩展功能用的,装饰器也是基于闭包函数实现的。

无参装饰器

# 有一个打印函数,这时候想给他添加一个计时功能统计程序运行总体时间
# 因为这个功能被广泛调用所以要在不能修改源码及调用方式的基础上来扩展功能
import time
def outer(func):  # 装饰器  添加了计算运行时间的功能 基于闭包函数
    def inner():

        time1=time.time()   # 开始时间
        func()             # 调用作为外层函数参数 传入的原函数
        time_total=time.time()-time1  # 程序运行总计时
        print("程序运行总时间",time_total)
    return inner



def prin():         # 原函数 
    time.sleep(2)  # 休眠2秒再运行程序
    print("hello world")
--------------------------------------------------------
prin=outer(prin)   # 这里的 outer(prin) 其实是他返回的inner 也就是prin =inner
--------------------------------------------------------
prin()   # 等于inner()

结果
hello world
程序运行总时间 2.011275291442871

可以看到已经实现了功能,并且没有改变函数源码及调用方式
被分隔的那一行可以替换成@outer 也就是语法糖,但是@outer需要写在被装饰函数的上方第一行,装饰器得写在被装饰函数的上方

import time
def outer(prin):  # 参数

    def inner():

        time1=time.time()   # 开始时间
        prin()        # 调用打印函数
        time_total=time.time()-time1  # 程序运行总计时
        print("程序运行总时间",time_total)
    return inner

--------------------------------
@outer 
--------------------------------
def prin():
    time.sleep(2)  # 休眠2秒再运行程序
    print("hello world")
----------------------------------------------------
# prin=outer(prin)   # 这里的 outer(prin) 其实是他返回的inner 也就是prin =inner*
----------------------------------------------------
prin()   # 等于inner()

如图所示 @outer 可以代替prin=outer(prin) 这是Python语法糖非常方便

若是原函数本身有参数及返回值,那装饰器也需要进行修改

修改后如下
import time
def outer(func):  

    def inner(*args,**kwargs):# 传入不定长参数

        time1=time.time()   # 开始时间
        res=func(*args,**kwargs)# 调用打印函数  同样传入不定长参数 并将返回值赋给res
        
        time_total=time.time()-time1  # 程序运行总计时
        print("程序运行总时间",time_total)
        return res   # 返回原函数的返回值
    return inner


@outer
def prin(a):
    time.sleep(2)  # 休眠2秒再运行程序
    print(a)
    return 123


prin(12313131313)  # 调用
结果

12313131313
程序运行总时间 2.0009818077087402

有参装饰器

有参装饰器
# 给原函数再加个认证功能
import time



def login_add(name,pwd):
    def outer(func):

        def inner(*args,**kwargs):# 传入不定长参数

            if name == 1 and pwd == 2:
                print("验证通过,程序运行")



                time1=time.time()   # 开始时间
                res=func(*args,**kwargs)# 调用打印函数  同样传入不定长参数 并将返回值赋给res

                time_total=time.time()-time1  # 程序运行总计时
                print("程序运行总时间",time_total)
                return res      # 返回原函数的返回值
            else:
                print("输入错误!请重新登录")


        return inner
    return outer



@login_add(1,3)  # 传了错误的参数
def prin(a):
    time.sleep(2)  # 休眠2秒再运行程序
    print(a)
    return 123
    
prin(11235)
#结果

输入错误!请重新登录


@login_add(1,2) # 把3改成2

prin(11235) # 再运行程序
# 结果 
验证通过,程序运行
11235
程序运行总时间 2.000922203063965

叠加多个装饰器

def a(func):
    def b():
        func()
        print("第一个装饰器")
    return b

def c(func):
    def d():
        func()
        print("第二个装饰器")
    return d
@a
@c
def  test():
    print("原函数")
test()
# 结果

原函数
第二个装饰器
第一个装饰器
"""

                                                    # 将d看做普通参数理解
@a             # -->  test=a(test) 注意这里的test= d   而a(d) =b  也就是左边的test=b  (由下而上)
@c             # -->  test=c(test) 这里的c() = d  也就是test =d


                # 定义由下而上 执行从上而下  结束是从下而上
                # 执行  注意看上面  @a=> test=a(test)=> a(d) => b  这里的参数func是d 而不是test 所以要先执行d
               
             d => c(test)   
             也就是@a 和@b  从上而下拆开 @a=> test=a(test) => a(d) => b  于是test()=b() 而b中的func()是d() 
             运行d() d中的func是原函数test   那么最终执行顺序	print("原函数")
                                                            print("第二个装饰器")
                                                            print("第一个装饰器")
                                                    
               """

总的来说
在函数定义阶段:执行顺序是从最靠近函数的装饰器开始,自内而外
在函数执行阶段:执行顺序由外而内,一层层执行,执行结束是由内而外

参考:
https://www.cnblogs.com/zhangxinhe/p/6963462.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
闭包是一个函数对象,它引用了一些在其定义时外部环境的变量。在Python中,闭包常常用来创建装饰装饰其实就是一个闭包,它接受一个函数作为参数,并返回一个替代版函数。闭包允许我们在不修改原始函数的情况下,添加一些额外的功能或逻辑。 一个典型的装饰的例子可以是这样的:在一个函数外面定义一个装饰函数,然后通过在要装饰的函数之前添加@装饰名称的语法糖,来应用装饰闭包装饰的实现机制是类似的,都是通过嵌套函数的方式来实现的。在闭包中,内部函数引用了外部函数的变量。而在装饰中,装饰函数接受一个函数作为参数,并返回一个内部函数,内部函数可以访问外部函数的变量。 在闭包装饰的实现过程中,都需要注意作用域的规则。在闭包中,内部函数可以访问外部函数的局部变量,而在装饰中,装饰函数可以访问被装饰函数的变量。 闭包装饰提供了一种灵活的方式来扩展函数的功能,使得我们可以在不修改原始函数的情况下,添加一些额外的逻辑。它们是Python中非常强大而且常用的特性。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [python中的闭包装饰解释](https://blog.csdn.net/qq_39567748/article/details/99596644)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *3* [python高级之装饰](https://blog.csdn.net/qq_35396496/article/details/109147229)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值