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. 函数名称后面跟了括号,就是执行,不跟括号就是一个函数引用,实际上就是又增加一个变量名去指向这个函数体。
相关推荐
B站计算机毕业设计之家4 小时前
智慧交通项目:Python+PySide6 车辆检测系统 YOLOv8+OpenCV 自定义视频 自定义检测区域 (源码+文档)✅
大数据·python·opencv·yolo·智慧交通·交通·车流量
java1234_小锋4 小时前
TensorFlow2 Python深度学习 - 深度学习概述
python·深度学习·tensorflow·tensorflow2·python深度学习
迈火5 小时前
PuLID_ComfyUI:ComfyUI中的图像生成强化插件
开发语言·人工智能·python·深度学习·计算机视觉·stable diffusion·语音识别
浔川python社7 小时前
《网络爬虫技术规范与应用指南系列》(xc—5)完
爬虫·python
MongoVIP8 小时前
Scrapy爬虫实战:正则高效解析豆瓣电影
python·scrapy
李小白668 小时前
Python文件操作
开发语言·python
weixin_525936339 小时前
金融大数据处理与分析
hadoop·python·hdfs·金融·数据分析·spark·matplotlib
Zwb2997929 小时前
Day 30 - 错误、异常与 JSON 数据 - Python学习笔记
笔记·python·学习·json
码界筑梦坊10 小时前
206-基于深度学习的胸部CT肺癌诊断项目的设计与实现
人工智能·python·深度学习·flask·毕业设计
flashlight_hi11 小时前
LeetCode 分类刷题:74. 搜索二维矩阵
python·算法·leetcode·矩阵