call函数的定义
__call__函数你可以把它理解为:让一个对象 “像函数一样被调用”的魔法入口
1)__call__函数到底是啥
class Test:
def __init__(self,name):
self.name = name
print("halo,__init__",self.name)
def __call__(self):
print("halo,__call__",self.name)
t = Test("lh")
上述代码执行结果
发现call函数并没有执行,那我们修改一下代码
class Test:
def __init__(self,name):
self.name = name
print("halo,__init__",self.name)
def __call__(self):
print("halo,__call__",self.name)
t = Test("lh")
t() #只加了一个这个
上述代码执行结果
发现call函数又打印 我们只是加了一个t(),所以得出结论
当对象像函数一样执行时,就会执行call函数
2)装饰器
在介绍call函数之前 我们先了解一下什么是装饰器
def test(name:str)->None:
print("name",name)
test("123")
执行上述代码 我们得到结果
OK!代码很简单 打印出来了我们想要的name。那现在我接着提出我的需求 ,比如我想在打印name前还打印一下当前时间。于是我们改动代码
from datetime import datetime
def test(name:str)->None:
print("当前时间是",datetime.now())
print("name",name)
test("123")
获取结果
也是很简单 。 随便加一个时间就出来了。但是如果有3个方法都需要加当前时间呢?
from datetime import datetime
def test1(name:str)->None:
print("当前时间是",datetime.now())
print("name",name)
def test2(name:str)->None:
print("当前时间是",datetime.now())
print("name",name)
def test3(name:str)->None:
print("当前时间是",datetime.now())
print("name",name)
test1("1")
test2("2")
test3("3")
执行结果
确实能实现我们的需求,但是每个方法都加上重复的代码的快对于后续优化来说也存在一些问题 于是就引了装饰器
引入函数装饰器
from datetime import datetime
def my_timer(func):
def wrapper(*args,**kwargs):
print("当前时间是:",datetime.now())
namer = func(*args,**kwargs)
return namer
return wrapper
@my_timer
def test(name:str)->str:
print("name",name)
return name
test("1")
test("2")
test("3")
获取结果
我们发现在不改变原函数test的情况下,增加了一个@函数,就可以实现刚才的所有逻辑。而@函数也就是我们说的装饰器
@函数的作用
看完上述代码。我们应该会有这样的一个疑问就是 @函数的作用是啥,
ok,那让我们一点点来分析
当程序自上而下执行到第10行时,也就是@函数这一行,其实是会把代码进行一次转换的转换为
test = my_timer(test) ,那执行my_timer(test),拿到的其实就是wrapper这个函数了。然后我们赋值给test(注意这里埋一个雷。等号左边的名称可以是A,B,C吗 ?如果不是test有啥影响?)然后代码继续往下执行执行到17行的时候即test("1")。会走wrapper的内部逻辑也就是从print("当前时间是")这行开始依次执行
整体执行逻辑图就是
代码定义阶段(执行到@my_timer那一步)
┌───────────────────────────────┐
│ 1. 解释器从上到下执行代码 │
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ 2. 定义 my_timer(func) │
│ (此时只是把函数对象创建出来)│
└───────────────┬───────────────┘
│
v
┌────────────────────────────────────────────┐
│ 3. 看到 @my_timer │
│ 解释器会先“创建原始 test 函数对象” │
│ original_test = <function test at ...> │
└───────────────┬────────────────────────────┘
│
v
┌────────────────────────────────────────────┐
│ 4. 执行装饰: my_timer(original_test) │
│ - func 形参接到 original_test │
│ - 在 my_timer 内部创建 wrapper │
│ - wrapper 闭包里记住 func=original_test │
│ - return wrapper │
└───────────────┬────────────────────────────┘
│
v
┌────────────────────────────────────────────┐
│ 5. 重新绑定名字: test = wrapper │
│ (从此 test 这个名字不再指向 original_test)│
└────────────────────────────────────────────┘
运行阶段(执行到test(1)那一步)
┌───────────────────────────────┐
│ 运行:调用 test("1") │
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ 实际调用的是 wrapper("1") │
│ 因为此时 test 指向 wrapper │
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ wrapper 内部: │
│ 1) print 当前时间 datetime.now()│
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ 2) namer = func("1") │
│ 注意:func 是闭包里保存的 │
│ original_test(原 test) │
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ original_test 执行: │
│ print("name", "1") │
│ return "1" │
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ wrapper 收到返回值 namer="1" │
│ return "1" │
└───────────────────────────────┘
看了下上面的,那在回到刚才那个问题 等号右边的必须是test吗?
我们写一下同等处理的代码
from datetime import datetime
from functools import wraps
def my_timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
print("当前时间是:", datetime.now())
return func(*args, **kwargs)
return wrapper
def test(name: str) -> str:
print("name", name)
return name
A = my_timer(test) # ✅ A 指向 wrapper
# test 仍然指向原 test
test("1") # ❌ 不走时间逻辑(因为调用的是原 test)
A("1") # ✅ 走时间逻辑(因为调用的是 wrapper)
发现等号右边接受的wrapper是那个,调用的函数就需要是那个
于是得出结论
-
左边这个名字可以不是test(比如A = my_timer(test)),但这样你的用A()调用才可以,test()调用还是原函数。
-
所以@函数默认把返回值赋给同名变量(test),保证装饰后仍然有原函数名来调用
引入类装饰器
OK,说完函数装饰器,回到主线这里 ,我们了解call函数是在对象被调用的时候主动执行的,所以当对象(test())在执行的时候是不是也可以加一些自定义逻辑来实现装饰器呢?
from datetime import datetime
class MyTimer:
def __init__(self,func):
self.func = func
def __call__(self,*args,**kwargs):
print("当前时间是:",datetime.now())
self.func(*args,**kwargs)
print("类装饰器执行结束")
@MyTimer
def test(name):
print("name",name)
test(1)
test(2)
test(3)
执行结果
OK基于上述代码我们看到我们已经实现了一个基本的类装饰器,那参考函数装饰器我们来捋一下类装饰器的执行流程
@函数的作用
基于函数装饰器,我们可以得到11行@MyTimer 等同于 test = MyTimer(test)
定义阶段,执行到@MyTimer时发生了什么
┌───────────────────────────────┐
│ 1) 解释器创建原 test 函数对象 │
│ original_test = MyTimer(test) │
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ 2) 执行 MyTimer(original_test) │
│ - __new__ 创建实例 instance │
│ - __init__(instance, func) │
│ 保存:instance.func = original_test │
└───────────────┬───────────────┘
│
v
┌───────────────────────────────┐
│ 3) 重新绑定:test = instance │
│ 现在 test 不再是函数,而是对象│
└───────────────────────────────┘
运行阶段,执行到test(1)时发生了什么
test(1)
│
└──► instance.__call__(1)->开始调用call函数
│
├─ print 当前时间
├─ instance.func(1) → 调用原 test(1)
│ └─ print("name", 1)
└─ print("类装饰器执行结束")
综上所述得到结论
-
test = MyTimer(test)这个的返回结果是mytimer拿到一个MyTimer的实例对象。
-
这个实例对象是new魔法函数创建并返回的(Super.new(MyTimer))创建出来的实例.
-
然后Python调用构造函数.执行self.func = func 即self.func = test
-
然后test(1)开始执行。因为test的是实例对象,所以接收到参数会自动执行call函数