Python——闭包与装饰器(闭包原理、nonlocal、装饰器(语法糖)原理、通用装饰器、多次装饰、带参数的装饰器、类装饰器)

闭包 closure

把组成内层函数的语句,以及这些语句所依赖的环境(即该内层函数使用到的非局部变量)一同打包,即为一个闭包,简单来说闭包是由一个函数和该函数使用到的该函数的外部变量共同组成的

闭包产生的原理

具体的描述

  • 由于Python中的函数可以嵌套定义,于是我们当然也可以在一个函数A中使用函数A的一些变量进行内部函数B的定义(即一个函数对象的创建),然后再通过return语句返回内部函数B(注意,要将该内部函数返回,才能产生闭包),让外部对内部函数B进行调用,这个内部函数B就是闭包(此时的内部函数B不仅仅包括定义的函数代码块,还包括那些被使用的函数A中的局部变量)
  • 这些被内部函数B使用的外部函数A中的局部变量非常特殊,因为正常来说,一个函数在执行return语句的时候,其内部的局部变量本来全部都会被销毁的,但是如果在这个函数A内部定义一个函数B,并且这个函数B为被return的对象,同时函数B会调用函数A中的一些局部变量,那么这些被调用的局部变量的生命周期会被延长(因为当函数A执行return语句的时候,会发现函数B以后要使用这些局部变量),即这些局部变量并不会被销毁,而是被保留下来,等到函数对象B被销毁以后(即del 函数B的时候)才会被连带着销毁
  • 外层函数可以看作为其内层函数的加工厂,根据给工厂输入的参数的不同,返回的内层函数也随之不同,同一个工厂中制造出来的闭包之间互不影响

简洁的描述

  • 如果使用了外部函数A的局部变量的一个内部函数B被该外部函数A通过return语句返回了,则这个内部函数B就是一个闭包
  • 那个被内部函数B使用的外部函数A的局部变量在返回后,就会作为闭包的一部分,这种变量有一个特点:要等到内部函数对象B被销毁以后(即del 函数B的时候)才会被连带着销毁,否则一直存在着
def func_out():
    data = [1, 2]

    def func_in():
        print(data)

    return func_in

closure_1 = func_out()
closure_2 = func_out()
print(closure_1)
print(closure_2)
closure_1()
closure_2()

#输出结果:
"""
<function func_out.<locals>.func_in at 0x000001F9634A4B80>
<function func_out.<locals>.func_in at 0x000001F96390A5E0>
[1, 2]
[1, 2]
"""

根据代码运行结果可以看出,每一次运行外部函数,得到的闭包是不一样的,其实本质上的原因和下面这个的代码得到的列表不一样的原因其实是相同的,创建一个列表和创建一个闭包,都是一样的操作,闭包说白了也就是一个特殊的函数对象(这个函数对象的某一些变量直到该函数对象被销毁以后才会被连带销毁,否则一直存在着),而Python中函数对象和列表、元组、字典…这些对象地位是平等的

def func_out():
    data = [1, 2]

    return data

lst_1 = func_out()
lst_2 = func_out()
print(id(lst_1))
print(id(lst_2))

#输出结果:
"""
2368830483392
2368830483520
"""

从下面这个代码我们可以再来理解一下闭包的定义和产生原理

def func_out():
    data = [1, 2]		#被内部函数使用的func_out中的局部变量
    print(id(data))
    
    def func_in():		#内部函数
        print(data)
        print(id(data))

    return func_in		#将内部函数返回

closure = func_out()
print(closure)
closure()				#外部对内部函数进行调用

#输出结果:
"""
3095596486272
<function func_out.<locals>.func_in at 0x000002D0BFD74B80>
[1, 2]
3095596486272
"""

"组成内层函数的语句"就是指def func_in中的代码块,“这些语句所依赖的环境"就是指位于这个代码块外部的"data = [1, 2]”,本来如果没有闭包的话,这个data在执行return语句后会被自动销毁,但是,我们在这个代码中使用了闭包,于是这个data是不会被销毁的而是,一直存在着(从打印结果中的两个data的id完全一致就可以看出)

由于data不会被销毁,我们还可以做到这种效果

def func_out():
    data = [1]

    def func_in():
        data.append(1)
        print(data)

    return func_in

closure = func_out()
closure()
closure()
closure()

#输出结果:
"""
[1, 1]
[1, 1, 1]
[1, 1, 1, 1]
"""

网上的一个经典案例,就是使用了闭包的这个特性:
要求实现一个人从(0,0)坐标原点开始,每一次用户输入移动的方向和步长,这个人就会进行相应移动,并打印出移动后的位置坐标

def create():
    start = [0, 0]      #起始坐标
    def person(direction, step):
        new_x = start[0] + direction[0] * step      #调整x值
        new_y = start[1] + direction[1] * step      #调整y值

        start[0] = new_x            #记录新坐标x值
        start[1] = new_y            #记录新坐标y值

        return [new_x, new_y]       #返回移动后的新坐标
    return person


p = create()
print(p([1, 0], 10))    #向x正方向移动了10个单元
print(p([0, 1], 5))     #向y正方向移动了10个单元
print(p([0, -1], 10))   #向x负方向移动了10个单元

#输出结果:
"""
[10, 0]
[10, 5]
[10, -5]
"""

闭包与nonlocal非局部变量

在外部函数A中的局部变量对于内部函数B来说究竟是什么类型的变量?(是局部变量还是全局变量)
首先肯定不是内部函数B的局部变量,内部函数B的局部是指在内部函数B的代码块中定义的变量

难道是全局变量?

data = [1, 2]
def func_out():
    data = [3, 4]

    def func_in():
        global data     #引用的是全局变量data
        print(data)

    return func_in

closure = func_out()
closure()

#输出结果:[1, 2]

从代码运行结果可以看出,在外部函数A中的局部变量对于内部函数B来说并不是全局变量

这里要引入一个新的概念:非局部变量
非局部变量其实是相对的一个称谓,是指既不是局部又不是全局的变量,我们在上面这个代码中,data就是属于非局部变量

如果内层函数要对外层函数中的非局部变量进行修改,就要使用nonlocal关键字,如果不使用该关键字,直接进行修改操作,就会抛出异常

验证外部函数中的data是否为非局部变量

data = [1, 2]
def func_out():
    data = [3, 4]

    def func_in():
        nonlocal data
        print(data)

    return func_in

closure = func_out()
closure()

#输出结果:[3, 4]

从代码运行结果可以看出,在外部函数A中的局部变量对于内部函数B来说是非局部变量

使用nonlocal关键字

def func_out():
    data = '12'

    def func_in():
        nonlocal data		#使用nonlocal关键字,使得可以对非局部变量值进行修改
        data += '34'
        print(data)

    return func_in

closure = func_out()
closure()

#输出结果:1234

程序正常运行,没有抛出异常

def func_out():
    data = '12'

    def func_in():		#未使用nonlocal关键字,尝试直接对非局部变量值进行修改
        data += '34'
        print(data)

    return func_in

closure = func_out()
closure()

#引发异常:local variable 'data' referenced before assignment

程序抛出异常,异常信息的意思就是局部变量data未进行定义,就开始修改数值了

有一些读者可能会想到,为什么一定要使用外部函数中的局部变量呢?在内部函数创建一个局部变量不就行了吗?
上面仅仅是为了引入闭包,让大家熟悉一下,所以上面在调用外部函数的时候,是没有传入任何实参的,而我们一般使用闭包,都会传入实参,使得构造出的闭包灵活多变

def func_out(data):

    def func_in():
        nonlocal data
        data += '34'
        print(data)

    return func_in

closure_1 = func_out('12')		#传入参数,构建第一个闭包
closure_1()
closure_2 = func_out('56')		#传入参数,构建第二个闭包
closure_2()

#输出结果:
"""
1234
5634
"""

由上面代码的运行结果可见,我们传入的参数不同,构造出的闭包调用后实现的效果也不同

当然,我们通过一个外部函数同时制造多个闭包并返回

def func_out():
    data = [10, 20]

    def func_in_1():
        print(data[0])

    def func_in_2():
        print(data[1])

    return func_in_1, func_in_2		#将两个闭包以一个元组的形式返回

closure_tpl = func_out()			#得到装有两个闭包的元组
closure_l = closure_tpl[0]			#取出第一个闭包
closure_2 = closure_tpl[1]			#取出第二个闭包
closure_l()			#调用第一个闭包
closure_2()			#调用第二个闭包

#输出结果:
"""
10
20
"""

当然,我们调用闭包的时候也是可以传入参数的

def func_out():
    data_1 = 10	
    
    def func_in(data_2):	#闭包设置相应的形参接收
        print(data_1 + data_2)

    return func_in	

closure = func_out()
closure(20)					#在调用闭包的时候传入参数
closure(30)	

#输出结果:
"""
30
40
"""

闭包与自定义对象的比较

计数器的自定义对象实现

class Count:
    def __init__(self, start):  #设置计数的初始值
        self.start = start

    def __call__(self):     #使得实例对象可以被调用
        self.start += 1     #计数的步长为1
        print(self.start)   #打印计数结果

counter = Count(0)          #设置计数的初始值为0,创建一个计数器
counter()                   #计数
counter()
counter()

#输出结果:
"""
1
2
3
"""

计数器的闭包实现

def Count(start):           #设置计数的初始值
    
    def counter():	
        nonlocal start
        start += 1          #计数的步长为1
        print(start)        #打印计数结果

    return counter	

counter = Count(0)          #设置计数的初始值为0
counter()	                #计数
counter()	
counter()

#输出结果:
"""
1
2
3
"""

闭包可以认为是轻量级的自定义对象,因为自定义类会自动继承object类的所有方法,所以对象的功能实际上是极其强大的(一般很多功能我们都没有使用到),但是也会消耗更多的内存,而闭包仅仅就提供一个功能而已(闭包相当于自定义对象中的一个方法而已,仅仅提供一个功能)

装饰器(语法糖)decorator

执行一个函数(即被装饰的函数)的时候,会变成执行另一个函数(即装饰器)中的代码。可以实现在不完全影响原有函数的功能的情况下(即不修改原函数代码),还能为被装饰的函数"添加"新的功能

装饰器的功能

  • 引入日志
  • 函数执行时间的统计
  • 执行函数前的预备处理 和 执行函数后的清理工作
  • 缓存

通过在函数定义(def语句之前)放置"@符号+装饰器名称"来对函数进行装饰

def decoration(func):   #装饰器
    return func

@decoration     #通过 @+装饰器名 的形式实现函数的装饰
def func():     #被装饰函数
    pass

"@符号+装饰器名称"可以放在def(即一个函数定义)或者一个class(即一个类定义)的前面,否则会抛出异常

def decoration(func):
    return func

@decoration
print(123)

#代码效果:无法运行,有语法错误SyntaxError: invalid syntax

一个函数装饰前后的调用效果对比

def decoration(func):
    print(123)
    return func

def func():		#不使用装饰器进行装饰
    print(456)

func()
#输出结果:456
def decoration(func):
    print(123)
    return func

@decoration
def func():		#使用装饰器decoration进行装饰
    print(456)

func()

#输出结果:
"""
123
456
"""

装饰器装饰的原理

示例代码

def decoration(func):
    print(123)
    return func

@decoration
def function():
    print(789)

print(456)
function()

#输出结果:
"""
123
456
789
"""

分析过程:

首先,要知道def语句就是一个创建函数对象的过程,将创建后的函数对象的引用保存在函数名中

def function():		#def语句创建了一个函数对象,将该函数对象的引用交给变量function
	pass		#即变量function保存了函数对象的引用
    
print(function)

#输出结果:<function function at 0x0000026FE45A58B0>

其次,要知道"@符号+装饰器名称"是一个可执行语句,而且会导致装饰器函数被调用,并且调用装饰器时传入的参数即为被装饰函数对象

def decoration(func):
    print("装饰器函数代码被执行")
    return func

@decoration
def function():
    print("被装饰函数代码被执行")

#输出结果:装饰器函数代码被执行
def decoration(func):
    print("装饰器函数代码被执行")
    print(func)		#打印func
    func()			#调用func
    print("装饰器函数代码执行结束")

    return func

@decoration
def function():
    print("被装饰函数代码被执行")

#输出结果:
"""
装饰器函数代码被执行
<function function at 0x0000017E178F5B80>
被装饰函数代码被执行
装饰器函数代码执行结束
"""

于是在示例代码中,解释器先运行代码"def decoration",创建了一个函数对象,接着看到"@decoration",先去运行代码"def function",又创建了一个函数对象,再将该函数对象作为参数,调用函数decoration,打印出"123"

我们看到函数decoration是有一个返回值func的(即将传入的函数func再返回回去),那么这个返回值正常情况下一定是有一个变量的接收的,其实这个变量就是原来的那个保存函数对象引用的function变量

def decoration(func):
    print("装饰器函数代码被执行")
    return 123

@decoration
def function():
    print("被装饰函数代码被执行")

print('此时的function变量保存的对象为:', function)

#输出结果:
"""
装饰器函数代码被执行
此时的function变量保存的对象为: 123
"""

于是在示例代码中,我们调用的是decoration函数的返回值(其实也就是原来的函数对象),前后的function保存的函数对象没有发生更改,一直都是那个打印789的函数

示例代码的运行过程:

  • 首先执行代码"def decoration",创建了一个函数对象,引用保存于decoration变量中
  • 接着看到代码"@decoration",先执行后面的代码"def func",创建了一个函数对象,引用保存于function变量中
  • 然后才会执行代码"@decoration",将function变量保存的引用作为参数,调用装饰器decoration,会打印出123
  • 装饰器decoration中代码运行完毕,返回的func(还是原来传入的那个还是对象的引用)赋值给了function变量,函数装饰过程结束
  • 代码继续往下运行,会打印456
  • 调用function,打印789

了解了示例代码的运行过程,我们可以写出一个等效的代码

def decoration(func):
    print(123)
    return func

def function():
    print(789)

function = decoration(function)		#即为装饰的过程

print(456)
function()

#输出结果:
"""
123
456
789
"""

装饰器+闭包

上面的示例代码中,装饰器是直接返回原来的函数对象,但是实际使用装饰器的时候,一般都是返回一个闭包

def decoration(func):
    print("---1---")
    def in_func():
        print("---4---")
        func()
        print("---5---")

    print("---2---")
    return in_func
    

@decoration
def function():
    print('amazing')

print("---3---")
print(function)
function()


#输出结果:
"""
---1---
---2---
---3---
<function decoration.<locals>.in_func at 0x0000021AFD8DA5E0>
---4---
amazing
---5---
"""

代码运行过程解析

  • 首先运行代码"def decoration",创建一个函数对象,并将引用保存于变量decoration中
  • 接着看到代码"@decoration",先执行代码"def function",创建一个函数对象,并将引用保存于变量function中
  • 然后才执行代码"@decoration",将function作为参数,调用函数decoration,于是会打印’—1—’
  • 继续运行函数decoration中的代码,创建一个函数对象,并将引用保存于变量in_func中,接着会打印’—2—’
  • 函数decoration返回这个使用了func变量的内部函数,闭包彻底形成,并将这个闭包赋值给了变量function(注意,此时装饰前后变量function保存的对象不是同一个对象了
  • 接着运行代码,会打印’—3—',接着打印function变量对应的对象,明显为一个闭包
  • 调用变量function(实际上是调用那个由decoration函数返回的闭包),运行闭包中代码,会打印’—4—‘,调用原来function保存的函数,会打印’amazing’
  • 继续运行闭包中代码,会打印’—5—',闭包运行结束,程序运行完毕

可以有一个基本的概念:装饰过程就是自动调用了一个函数,完成了一个闭包构建过程,并将这个闭包交给我们调用,我们感觉上调用的还是原来的函数,但是实际上被调用的对象已经被悄悄替换了

等效代码

def decoration(func):
    print("---1---")
    def in_func():
        print("---4---")
        func()
        print("---5---")

    print("---2---")
    return in_func
    
def function():
    print('amazing')

function = decoration(function)

print("---3---")
print(function)
function()

#输出结果:
"""
---1---
---2---
---3---
<function decoration.<locals>.in_func at 0x000002280C8785E0>
---4---
amazing
---5---
"""

装饰器的通常用法

一般来说,装饰器中代码仅仅就是创建内层函数,然后返回一个闭包,一般不会进行其他的操作。在闭包中,所有额外功能的相关代码,都添加在调用被装饰函数代码的前后

from time import time
def Time(fun):                  #为被装饰函数添加"计算代码运行时间"的功能
    def test_time():            #定义内层函数
        #额外功能
        start_time = time()     
        print("程序开始运行")

        fun()   #调用被装饰函数

        #额外功能
        end_time = time()       
        print("程序结束运行") 

        print("该程序的运行时间大致为:", end_time - start_time)

    return test_time            #返回内层函数


@Time		#进行函数装饰
def F():	#定义被装饰函数
    lst = []
    for i in range(1000000):
        lst.append(i)

F()			#调用函数

#输出结果:
"""
程序开始运行
程序结束运行
该程序的运行时间大致为: 0.9342632293701172
"""

其实不用装饰器也可以实现相同效果

from time import time

def F():
    lst = []
    for i in range(1000000):
        lst.append(i)

start_time = time()
print("程序开始运行")

F()     #调用函数

end_time = time()
print("程序结束运行")

print("该程序的运行时间大致为:", end_time - start_time)

#输出结果:
"""
程序开始运行
程序结束运行
该程序的运行时间大致为: 1.0231080055236816
"""

虽然在代码量看来,似乎没有使用装饰器的代码更加好,但是如果需要对于多个函数代码的执行时间进行测试,那明显使用装饰器更加简洁方便

通用装饰器

实现任意数量(包括0)形参的函数兼容

实际上就是使用不定长参数进行实参接收,调用被装饰函数的时候,将这些参数进行解包后再传入

def decoration(fun):
    def F(*args, **kwargs):
        fun(*args, **kwargs)

    return F

@decoration
def A():
    print('A函数被正常调用')

@decoration
def B(x1):
    print('B函数被正常调用', x1)

@decoration
def C(x1, x2):
    print('C函数被正常调用', x1, x2)

A()
B(1)
C(1, 2)

#输出结果:
"""
A函数被正常调用
B函数被正常调用 1
C函数被正常调用 1 2
"""
实现任意数量(包括0)返回值的函数兼容

由于多个元素之间使用逗号隔开,就会自动构成一个元组对象,于是这些函数返回的实际上就是一个对象,于是我们只要在闭包内部对被装饰函数运行完毕的返回值接收,再返回到闭包外部即可

def decoration(fun):
    def F():
        r = fun()
        return r
        
    return F
        

@decoration
def A():
    print('A函数被正常调用')   #无return即为return None

@decoration
def B():
    print('B函数被正常调用')
    return 1

@decoration
def C():
    print('C函数被正常调用')
    return 1, 2


print(A())
print(B())
print(C())

#输出结果:
"""
A函数被正常调用
None
B函数被正常调用
1
C函数被正常调用
(1, 2)
"""
将上面两种装饰器进行整合,即为一个通用装饰器
def decoration(fun):	#通用装饰器
    def F(*args, **kwargs):
        r = fun(*args, **kwargs)
        return r

    return F

使用多个装饰器装饰同一个函数

def decoration1(fun):
    print("---2---")

    def in_func():
        print("---3---")
        fun()

    return in_func

def decoration2(fun):
    print("---1---")

    def in_func():
        print("---4---")
        fun()

    return in_func
        
@decoration1
@decoration2
def function():
    print("---5---")

function()

#输出结果:
"""
---1---
---2---
---3---
---4---
---5---
"""

代码运行过程解析

  • 整体的装饰过程就是先执行代码"@decoration2",即使用装饰器decoration2进行装饰,再执行代码"@decoration1",即使用装饰器decoration1进行二次装饰
    • 首先创建两个函数对象,将它们的引用分别保存在decoration1和decoration2中,看到代码"@decoration2",先执行代码"@decoration1",看到代码"@decoration1",先执行代码"def function",创建一个函数对象,并将引用保存在function中
    • 接着才执行代码"@decoration2",将function作为参数,调用decoration2
      • 会打印’—1—‘,然后创建一个内层函数,并将引用保存在局部变量in_func中,将该使用了fun变量(对应的函数对象可以打印’—5—‘)的内层函数返回,闭包形成,赋值给function,即该闭包引用保存在function中(对应的函数对象可以打印’—4—‘和’—5—')
    • 最后执行代码"@decoration1",再次将function作为参数,调用decoration1
      • 会打印’—2—‘,然后创建一个内层函数,并将其引用保存在局部变量in_func中,将该使用了fun变量(对应的函数对象可以打印’—4—‘和’—5—‘)的内层函数返回,闭包形成,赋值给function,即该闭包引用保存在function中(对应的函数对象可以打印’—3—‘、’—4—‘和’—5—')
    • 最后调用function变量,会打印出’—3—‘、’—4—‘和’—5—’

等效代码

def decoration1(fun):
    print("---2---")

    def in_func():
        print("---3---")
        fun()

    return in_func

def decoration2(fun):
    print("---1---")

    def in_func():
        print("---4---")
        fun()

    return in_func
        
def function():
    print("---5---")

function = decoration2(function)	#第一次对function装饰
function = decoration1(function)	#第二次对function装饰

function()

#输出结果:
"""
---1---
---2---
---3---
---4---
---5---
"""

具有参数的装饰器

def decoration(lines):
    print(lines)

    def in_func(func):
        print("---2---")

        def inin_func():
            print("---3---")
            func()

        return inin_func

    return in_func

@decoration("---1---")       
def function():
    print("---4---")

function()

#输出结果:
"""
---1---
---2---
---3---
---4---
"""

代码运行过程解析

  • 整体的装饰过程就是先执行代码"decoration(“—1—”)“,即以”—1—"作为参数,调用装饰器decoration,再将function作为参数,对装饰器decoration的返回值进行调用,从而实现对装饰过程
    • 首先执行代码"def decoration",创建一个函数对象,并将引用保存在变量decoration
    • 看到代码"@decoration(“—1—”) “,先以”—1—“作为参数,调用装饰器decoration,会打印’—1—',创建闭包,并进行返回。然后执行代码"def function”,创建一个函数对象,并将引用保存在function中
    • 此时才真正开始对函数进行装饰,即将function作为参数,调用decoration的返回值(即那个闭包in_func),会打印’—2—',创建一个闭包,并进行返回。function变量进行接收
    • 接着执行代码,调用function,会打印’—3—‘,在内部调用function原来的函数,会打印’—4—’

等效代码

def decoration(lines):
    print(lines)

    def in_func(func):
        print("---2---")

        def inin_func():
            print("---3---")
            func()

        return inin_func

    return in_func

in_func = decoration("---1---")

def function():
    print("---4---")

function = in_func(function)

function()

#输出结果:
"""
---1---
---2---
---3---
---4---
"""

装饰器+可调用的实例对象

装饰器函数其实只需要返回一个可调用的对象即可,并不是非得要一个闭包,还可以是具有__call__方法的示例对象

class C:
    def __init__(self, func):
        print('正在初始化示例对象')
        self.func = func

    def __call__(self):
        print('示例对象被调用')
        self.func()


def decoration(func):
    print('---1---')
    ins = C(func)
    print('---2---')
    return ins

@decoration
def function():
    print("被装饰函数代码被执行")

print('---3---')
print(function)
function()

#输出结果:
"""
---1---
正在初始化示例对象
---2---
---3---
<__main__.C object at 0x0000020C34ACB3D0>
示例对象被调用
被装饰函数代码被执行
"""

代码运行过程解析

  • 首先运行代码"class C"及其内部代码,创建一个具有__call__方法的类对象,并将其引用保存在变量C中
  • 接着运行代码"decoration",创建一个函数对象,并将其引用保存在decoration中
  • 然后看到代码"@decoration",先执行后面的代码"def function",创建一个函数对象,并将其引用保存在变量function中
  • 最后才会执行代码"@decoration",将function作为参数,调用函数decoration的,会打印’—1—’
  • 继续运行decoration中代码,会执行代码"ins = C(func)",以function函数作为参数,实例化对象,会打印’正在初始化示例对象’,decoration中代码执行完毕,返回这个可调用的示例对象,赋值给变量function(注意,此时装饰前后变量function保存的对象不是同一个对象了
  • 继续运行代码,会打印’—3—',接着打印function变量对应的对象,明显为一个类型为C的实例对象
  • 这个实例对象被调用(实际上是调用那个由decoration函数返回的示例对象),会自动调用示例对象的__call__方法,会打印"示例对象被调用",并调用先前function变量保存的函数,会打印"被装饰函数代码被执行"

等价代码

class C:
    def __init__(self, func):
        print('正在初始化示例对象')
        self.func = func

    def __call__(self):
        print('示例对象被调用')
        self.func()


def decoration(func):
    print('---1---')
    ins = C(func)
    print('---2---')
    return ins

def function():
    print("被装饰函数代码被执行")

function = decoration(function)

print('---3---')
print(function)
function()

#输出结果:
"""
---1---
正在初始化示例对象
---2---
---3---
<__main__.C object at 0x000002A598BBB3A0>
示例对象被调用
被装饰函数代码被执行
"""
类装饰器

其实这个过程可以再稍微压缩一下,即直接可以将一个类对象作为装饰器(简称为类装饰器)

class C:
    def __init__(self, func):
        print('正在初始化示例对象')
        self.func = func

    def __call__(self):
        print('示例对象被调用')
        self.func()

@C
def function():
    print("被装饰函数代码被执行")

print(function)
function()

#输出结果:
"""
正在初始化示例对象
<__main__.C object at 0x000002358683AAF0>
示例对象被调用
被装饰函数代码被执行
"""

代码运行过程解析

  • 首先运行代码"class C"及其内部代码,创建一个具有__call__方法的类对象,并将其引用保存在变量C中
  • 接着看到代码"@C",先执行代码"def function",创建一个函数对象,并将引用保存在变量function中
  • 然后将function作为参数,调用类对象C,即创建一个实例对象,所以会打印’正在初始化示例对象’,将创建的实例对象赋值给function
  • 继续运行代码,打印function,明显这就是类型为C的实例对象,调用该实例对象,会自动调用实例对象的__call__方法,会打印’示例对象被调用’,再调用function原来保存的函数,会打印’被装饰函数代码被执行’

等价代码

class C:
    def __init__(self, func):
        print('正在初始化示例对象')
        self.func = func

    def __call__(self):
        print('示例对象被调用')
        self.func()

def function():
    print("被装饰函数代码被执行")

function = C(function)

print(function)
function()

#输出结果:
"""
正在初始化示例对象
<__main__.C object at 0x000001AF95D9ACD0>
示例对象被调用
被装饰函数代码被执行
"""

相应的,类装饰器也可以传入参数

class C:
    def __init__(self, data):
        print('正在初始化示例对象')
        print('传入的参数为', data)
        self.data = data

    def __call__(self, func):
        print('示例对象被调用')
        return func

@C(123)
def function():
    print("被装饰函数代码被执行")

print(function)
function()

#输出结果:
"""
正在初始化示例对象
传入的参数为 123
示例对象被调用
<function function at 0x0000021903335820>
被装饰函数代码被执行
"""

代码运行过程解析

  • 首先运行代码"class C"及其内部代码,创建一个具有__call__方法的类对象,并将其引用保存在变量C中
  • 看到代码"@C(123)“,会先以123作为参数,调用类对象C,得到一个实例对象,于是会打印’正在初始化示例对象’和’传入的参数为 123’,再执行代码"def function”,创建一个函数对象,并将引用保存在变量function中
  • 接着才会开始进行装饰,以function作为参数,调用实例对象,会打印’示例对象被调用’,返回原来的函数对象,赋值给function变量
  • 继续执行代码,对function进行打印,明显这是一个函数对象
  • 调用该函数对象,会打印’被装饰函数代码被执行’

等效代码

class C:
    def __init__(self, data):
        print('正在初始化示例对象')
        print('传入的参数为', data)
        self.data = data

    def __call__(self, func):
        print('示例对象被调用')
        return func

ins = C(123)
def function():
    print("被装饰函数代码被执行")

function = ins(function)

print(function)
function()

#输出结果:
"""
正在初始化示例对象
传入的参数为 123
示例对象被调用
<function function at 0x000001B0E5165820>
被装饰函数代码被执行
"""
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值