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 小时前
Claude Code辅助测试:导入篇skills
python·自动化
whcyhhh6 小时前
头歌实践教学平台:大数据存储2023(六)
大数据·数据库·python
for_ever_love__6 小时前
python基础语法学习: 闭包
开发语言·python·学习·闭包
ly76897 小时前
Python 全面入门:从核心语法到工程实践
开发语言·python
Niuguangshuo8 小时前
DeepFilterNet 3:端侧实时全带降噪的开源标杆
python
第一程序员9 小时前
AI 辅助编程的 7 个误区:把模型当高级搜索引擎是对它的最大浪费
python·rust·github
Groundwork Explorer10 小时前
W5500 网卡编程初始化与使用指南
python·单片机
逸Y 仙X10 小时前
MCP(模型控制协议)完全指南:从核心概念到实战开发
python·大模型·llm·ai编程·mcp
WILLF11 小时前
前端视角:Python requests vs JS axios
前端·python
哒哒哒52852011 小时前
69. Python: 核心语法-面向对象基础-案例(教务系统)
python