python中的闭包和装饰器

闭包

在了解装饰器之前,先要了解闭包。闭包就是函数内部保存了一些变量信息。

比如下面的例子

python 复制代码
def outer(x):  
    def inner(y):  
        return x+y  
    return inner   
  
closure = outer(5)
print(closure)  
#输出: <function outer.<locals>.inner at 0x00000198065C6F20>

print(closure.__name__)
#输出:inner

在上面的代码中,当执行 outer(5)的时候,返回的是inner这个函数引用^1^。也就是说上面的closure是一个函数,打印出来也确实是一个函数对象。把closure这个函数的名称打印出来,是inner,也印证了实际上closure这个变量指向的是inner这个函数。

前面说闭包是保存了一些变量信息,实际上在这里就是说closure这个变量指向的函数,也就是inner这个函数保存了那个数字5这个变量的值,5这个值是在inner的外层函数outer中输入的,由于inner还有closure指向它,所有它没有在内存中注销,就保存了这个值。

装饰器

理解了闭包,再来看装饰器。

装饰器本质上,是把函数作为参数传递给另外的函数来执行。

比如下面的例子

python 复制代码
def decorator(func):  
    def wrapper(*args, **kwargs):  
        print('我是装饰器中要执行的内容')  
        return func(*args, **kwargs)  
    return wrapper  


def say_hello():
    print(f'{say_hello.__name__}')
    print(f'hello')
    
    
say_hello = decorator(say_hello)
print('------')
say_hello()
#输出:
# ------
# 我是装饰器中要执行的内容
# wrapper
# Hello

  

# 在python中有一种语法可以用来使用装饰器,和上面这个完全等价

@decorator
def say_hello():
    print(f'{say_hello.__name__}')
    print(f'hello')

say_hello()
#输出:
# 我是装饰器中要执行的内容
# wrapper
# Hello

上面例子中,写法一和写法二是完全等价的。 写法一:

python 复制代码
say_hello = decorator(say_hello) 

写法二:

python 复制代码
@decorator
def say_hello()
	print(f'hello')

在这两种写法里面,实际上是分了两个步骤来执行:

  1. 第一步找到say_hello,这个变量实际上指向的是wrapper对象。同时又把say_hello这个变量的引用传给给了装饰函数decorator中闭包的func变量,这个func变量此时指向的就是原来say_hello指向的那个函数体。
  2. 第二步是执行say_hello,就是那个括号,也就是wrapper(),这个执行下来,所以function的名称就是wrapper

注意:上面的say_hello 实际上是decorator(say_hello)的执行结果,也就是decorator函数的返回值,是wrapper这个对象,返回的是对wrapper对象的引用,并没有执行decorator这个函数内部的wrapper函数。所以这里就会存在闭包的现象,如果在decorator中定义了参数,那么wrapper就会记住这个变量值,在这里就是记住的say_hello这个变量的值,就是那个最初的函数体,也就是闭包的概念了。

python 复制代码
def decorator(func):  
    def wrapper(*args, **kwargs):  
        print('我是装饰器中要执行的内容')
        print(f'decorator 中的func 的id是{id(func)}')
        return func(*args, **kwargs)  
    return wrapper  

  
def say_hello():
    print(f'{say_hello.__name__}')
    print(f'hello')

print(f'say_hello id is {id(say_hello)}')

say_hello = decorator(say_hello)
print('---')
say_hello()

# 输出:
# 注意看func的内存地址和最初的say_hello的内存地址是一样的
# say_hello id is 2307155184192
# ---
# 我是装饰器中要执行的内容
# decorator 中的func 的id是2307155184192
# wrapper
# hello

所以就会引出带参数的装饰器,装饰器工厂。

带参数的装饰器

还是上面的例子,请看

python 复制代码
def decorator_para(para):  
    def decorator(func):  
        def wrapper(*args, **kwargs):  
            print(f'我是装饰器中要执行的内容-{para}')  
            return func(*args, **kwargs)  
        return wrapper  
    return decorator  
  
  
# @decorator_para('装饰器参数1')  
def say_hello():  
    print(f'{say_hello.__name__}')  
    print(f'Hello')  
  
  
say_hello = decorator_para('装饰器参数1')(say_hello)  
  
  
say_hello()

这里 写法一:

python 复制代码
@decorator_para('装饰器参数1')  
def say_hello():  
    print(f'{say_hello.__name__}')  
    print(f'Hello')  

写法二:

python 复制代码
say_hello = decorator_para('装饰器参数1')(say_hello)  

也是等价的。 同样也是分为了两步:

  1. 第一步先去执行了decorator_para('装饰器参数1')得到了真正的装饰器decorator,而decorator_para('装饰器参数1')执行的结果是返回了decorator函数。
  2. 第二步找到say_hello,这个变量实际上指向的是decorator(say_hello)返回的对象,也就是wrapper,注意传入的parawrapper中用到了,被引用了,也就被wrapper记住了,这里就是闭包的应用了。接下来就和不带参数的装饰器相同了

重叠的装饰器

还是举例说明

python 复制代码
def decorator1(func):  
    def wrapper1(*args, **kwargs):  
        print('我是装饰器中1要执行的内容')  
        return func(*args, **kwargs)  
    return wrapper1  
  
  
def decorator2(func):  
    def wrapper2(*args, **kwargs):  
        print('我是装饰器中2要执行的内容')  
        return func(*args, **kwargs)  
    return wrapper2  
  
  
@decorator1  
@decorator2  
def say_hello():  
    print(f'{say_hello.__name__}')  
    print(f'Hello')  
  
  
say_hello = decorator1(decorator2(say_hello))  
  
  
say_hello()
#输出:
# 我是装饰器中1要执行的内容
# 我是装饰器中2要执行的内容
# wrapper1
# Hello

上面的两种写法是完全一样的。 在执行say_hello()的时候实际上分为了几个步骤:

  1. 首先say_hello指向的逻辑是:
    1. decorator2(say_hello)的返回对象是wrapper2
    2. decorator1(wrapper2)的返回对象是wrapper1
  2. say_hello()执行顺序:
    1. 先执行wrapper1(wrapper2),在此之前就会执行wrapper1中的打印内容
    2. wrapper2(say_hello),在此之前就执行wrapper2的打印内容
  3. 所以最后say_hello指向的是wrapper1,打印say_hello.__name__才会打印成wrapper1. 可以把堆叠装饰器想象成洋葱:当调用函数时,程序会先穿过外层装饰器,再到内层装饰器,最后执行原函数;当原函数执行完毕后,会按照相反的顺序退出各个装饰器。

Footnotes

  1. 函数名称后面跟了括号,就是执行,不跟括号就是一个函数引用,实际上就是又增加一个变量名去指向这个函数体。
相关推荐
这里有鱼汤5 小时前
小白必看:QMT里的miniQMT入门教程
后端·python
TF男孩15 小时前
ARQ:一款低成本的消息队列,实现每秒万级吞吐
后端·python·消息队列
该用户已不存在20 小时前
Mojo vs Python vs Rust: 2025年搞AI,该学哪个?
后端·python·rust
站大爷IP1 天前
Java调用Python的5种实用方案:从简单到进阶的全场景解析
python
用户8356290780511 天前
从手动编辑到代码生成:Python 助你高效创建 Word 文档
后端·python
c8i1 天前
python中类的基本结构、特殊属性于MRO理解
python
liwulin05061 天前
【ESP32-CAM】HELLO WORLD
python
Doris_20231 天前
Python条件判断语句 if、elif 、else
前端·后端·python
Doris_20231 天前
Python 模式匹配match case
前端·后端·python