尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

装饰器代码的执行顺序

装饰器代码的执行顺序 Python 装饰器的定义装饰器decorator是 Python 中的一种高级功能用于在不修改原函数代码的前提下动态扩展函数或类的功能。本质上装饰器是一个函数它接收一个函数作为参数并返回一个新的函数通常是对原函数的增强版本。装饰器通过 decorator_name 语法应用在函数或方法定义之前。Python 还提供了一些内置装饰器例如类的静态方法 staticmethod 和 类方法classmethod。常见应用场景日志记录记录函数调用信息、参数和返回值性能统计统计函数执行时间权限控制限制函数访问权限缓存缓存函数结果提高性能函数装饰器它接收一个函数作为参数并返回一个新的函数通常是对原函数的增强版本。类装饰器类装饰器接收一个类并返回修改后的类或包装类。增强类方法控制实例化过程实现单例、日志等功能Python 四种装饰器实现对比装饰器本质xxx 等价于被装饰对象 xxx(被装饰对象)1️⃣ 普通两层函数装饰器装饰函数def deco(func): def wrapper(*args,**kwargs): #前置 res func(*args,**kwargs) #后置 return res return wrapper deco def foo():pass foo()输入原始函数对象func返回内层 wrapper 函数对象调用foo()→执行wrapper()用途给函数增加前置 / 后置逻辑执行时机deco运行deco(原始foo)返回 wrapper变量 foo 指向 wrapperfoo()运行 wrapper 内部代码wrapper 内部调用原始 func补充运行示例def decorator_function(original_function): print(【A】进入装饰器decorator_function收到原函数对象) def wrapper(*args, **kwargs): print(【C】进入wrapper包装函数) # ⚠️这里只是定义wrapper这行print此时不会执行 print(执行前) result original_function(*args, **kwargs)#这里original_function后有所以属于调用函数这句话只是把函数的调用结果给result并不是用原函数对象替换所以下面的return result只是返回函数调用的结果与外层的return wrapper不同 print(执行后) return result#return后的内容可被函数外的变量调用 print(【B】装饰器返回wrapper函数对象) return wrapper#wrapper后未加不调用只是一个函数对象所以可以说是用wrapper函数对象替换原函数 print(--------1.开始定义函数遇到装饰器--------) decorator_function def target_function(): print(原函数执行) print(--------2.此时还没有调用target_function()--------) print(--------3.现在调用 target_function()--------) target_function() # ✅只有走到这一行wrapper内部代码才会运行才会输出【C】运行步骤运行print(--------1.开始定义函数遇到装饰器--------)→打印 1 号分割线碰到decorator_function相当于target_function decorator_function(target_function)把原始target_function( print(原函数执行) )传入decorator_function执行print(【A】...)→输出【A】执行def wrapper(...)仅仅定义 wrapper 函数wrapper 里面所有代码完全不执行包括里面的 print【C】只是被存起来不会打印执行print(【B】...)→输出【B】return wrapper完成装饰替换。往下执行print(--------2.此时还没有调用target_function()--------)→打印 2 号分割线往下执行print(--------3.现在调用 target_function()--------)→打印 3 号分割线执行target_function()→此时才真正进入 wrapper 函数内部执行print(【C】进入wrapper包装函数)→输出【C】接着打印执行前result original_function(*args, **kwargs)这里的 original_function是定义装饰器时后面写的函数对象original_function也就是第一轮decorator_function执行时输入的函数对象target_function()他在第一轮运行结束时由于wrapper内部还需要所以被保留了下来。执行target_function()函数对象输出“原函数执行”最后打印 执行后。所以真实输出顺序--------1.开始定义函数遇到装饰器-------- 【A】进入装饰器decorator_function收到原函数对象 【B】装饰器返回wrapper函数对象 --------2.此时还没有调用target_function()-------- --------3.现在调用 target_function()-------- 【C】进入wrapper包装函数 执行前 原函数执行#此处是函数运行过程中打印出来的内容文字只是展示不会交给外部变量原函数没有return,如果此时打印result返回结果是None,区分return和print 执行后一句话总结关键点decorator_function碰到就立刻执行 →输出 A、Bwrapper只定义不执行直到写target_function()加括号调用才输出 C 以及后面内容Python 从上到下跑代码定义完装饰与 target 之后继续向下跑后面的 print 打印 2、3 分割线跑完分割线才执行target_function()触发 wrapper。函数内的闭包对象是谁主要职责什么时候执行original_function被传进去的原始函数对象没被装饰的原版target_function执行业务本身逻辑做原本该做的事在wrapper内部手动调用original_function()才会跑decorator_function装饰器外层函数后面写的那个1. 接收原始函数2. 定义 wrapper3. 把 wrapper 返回出去加载阶段碰到就执行一次只跑 1 次调用被装饰函数时它不会再跑wrapper内层嵌套函数闭包函数1. 前置逻辑执行前2. 调用原始函数3. 后置逻辑执行后4. 返回原函数结果每次调用target_function()wrapper 就跑一遍调用多少次就跑多少次闭包在这里发生了什么通俗版decorator_function运行结束后这个函数的栈本来应该销毁局部变量original_function本该消失。 但是wrapper 内层函数引用了这个变量Python 闭包机制把original_function这个原始函数对象保留下来wrapper 随时可以调用它。比喻decorator_function工厂一次性造出外壳 wrapper造完工厂就关门。wrapper造出来的外壳工厂关门了外壳手里还攥着原版零件original_function每次使用外壳外壳就调用原版零件干活。2️⃣ 带参数三层函数装饰器装饰函数def repeat(n): def deco(func): def wrapper(*args,**kwargs): for _ in range(n): func(*args,**kwargs) return wrapper return deco repeat(3) def hello():pass hello()repeat(3)先执行repeat(3)得到中层deco等价hello repeat(3)(hello)decorate(hello)返回内层wrapper函数对象用途装饰的时候传入自定义参数执行时机repeat(3)执行最外层repeat(3)得到 deco再执行deco(原始hello)得到 wrapperhello()执行 wrapper 内部逻辑语法糖等价展开# repeat(3) 等价下面两行 decorator repeat(3) # 第一步调用外层repeat参数num_times3 say_hello decorator(say_hello) # 第二步拿到decorator传入原始say_hello三层函数各自职责表函数角色什么时候执行作用repeat(num_times)最外层接收装饰器参数repeat (3) 的时候立刻执行一次接收参数num_times返回中层decoratordecorator(func)中层真正的装饰器decorator(原始函数)执行一次接收被装饰业务函数func返回内层wrapperwrapper(*args,**kwargs)包装函数每次调用say_hello()就执行一遍写增强逻辑这里是循环内部调用原始func闭包wrapper 可以访问外层的num_times和func哪怕repeat、decorator函数已经执行结束退出。补充完整分阶段执行流程阶段 1代码加载遇到repeat(3)还没调用 say_hello ()Python 读到repeat(3)执行repeat(3)num_times 3定义内部函数decorator(func)只定义不跑 decorator 内部代码return decorator→把 decorator 函数对象返回现在等价变成decorator因为有所以执行的say_hello decorator(原始say_hello)还没此时还没用到say_hello现在执行say_hello decorator(原始say_hello)进入decorator(func)形参func接收原始say_hello函数对象定义内层wrapper(*args,**kwargs)仅仅定义 wrapperwrapper 内部代码不执行for 循环不会跑return wrapper变量say_hello现在指向wrapper对象。✔阶段 1 全部做完没有打印任何 Hello此时闭包保存两个变量num_times 3来自最外层 repeatfunc 原始say_hello来自中层 decorator阶段 2执行say_hello()→等价调用wrapper()现在跑 wrapper 函数体内部代码for _ in range(num_times):→循环 3 次num_times3闭包记住的值循环体内func(*args, **kwargs)调用原始的say_hello()打印Hello!循环执行 3 次输出三行 Hello!wrapper 没有写 return所以返回值是 None。3️⃣ 函数形式的类装饰器装饰器是用def定义的def log_class(cls): class Wrapper: def __init__(self, *args, **kwargs): self.wrapped cls(*args, **kwargs) def __getattr__(self, name): return getattr(self.wrapped, name) def display(self): print(调用前) self.wrapped.display() print(调用后) return Wrapper log_class#将MyClass类替换为Wrapper类 class MyClass: def display(self): print(原方法) obj MyClass()#实例化wrapper定义init、getattr、display三个方法 obj.display()#调用display方法输出调用前 原方法 调用后输入原始类对象cls返回全新包装类 WrapperMyClass log_class(原始MyClass)obj MyClass()→实例化 Wrapper内部再实例化原始类保存到self.wrapped用途代理包装类的实例可重写方法用__getattr__转发未定义方法执行时机log_class运行log_class(原始MyClass)定义并返回 Wrapper 类变量 MyClass 指向 Wrapperobj MyClass()触发 Wrapper 的__init__内部创建原始类实例obj.display():调用display方法4️⃣类形式的类装饰器装饰器是用class定义的class SingletonDecorator: def __init__(self, cls):#init这里的参数就是实例化这个类需要的参数对应def(xx)括号内的xx self.cls cls#cls后面没有加所以这里是直接把原始类Database指向self.cls变量名Database不再指向原来的 Database 类初始化后指向 SingletonDecorator 的实例对象self.cls。 self.instance None def __call__(self, *args, **kwargs): if self.instance is None: self.instance self.cls(*args, **kwargs)#这里self.cls后面加了所以这里创建了原始类的实例 return self.instance#缩进在def内因而执行SingletonDecorator实例化这一步时只是定义了def不会执行return这一步只有在后面调用__call__方法时才会执行returnreturn跟if同级顺序执行 SingletonDecorator class Database: def __init__(self): print(初始化) db1 Database() db2 Database() print(db1 is db2)SingletonDecorator等价Database SingletonDecorator(原始Database)返回装饰器类的实例对象不再是类、不是函数依靠魔法方法__call__对象加括号Database()自动调用__call__用途需要在装饰器上保存状态比如保存唯一实例self.instance执行时机SingletonDecorator实例化SingletonDecorator触发__init__保存原始类到self.clsDatabase()调用装饰器实例触发__call__在里面控制原始类实例化补充完整的分阶段执行流程阶段1代码加载遇到SingletonDecorator1.Python 执行class Database:创建原始 Database 类对象。2.SingletonDecorator等价执行Database SingletonDecorator(原始Database)3.实例化SingletonDecorator(原始Database)进入它的__init__def __init__(self, cls): self.cls cls # self.cls 原始Database类 self.instance None # 实例变量用来保存唯一对象初始None4.变量名Database不再指向原来的 Database 类现在指向 SingletonDecorator 的实例对象。⚠️重点 现在Database不是类了是一个装饰器类的实例对象。 因为实现了__call__所以这个对象可以加括号调用Database()。__call__当对实例对象加括号对象()的时候自动执行__call__方法。阶段 2db1 Database()Database是 SingletonDecorator 的实例写Database()触发__call__(*args,**kwargs)def __call__(self, *args, **kwargs): if self.instance is None: self.instance self.cls(*args, **kwargs) return self.instance判断self.instance is None→条件成立self.cls(*args,**kwargs)→原始Database()调用原始类的__init__打印初始化创建原始 Database 实例赋值给self.instance保存起来返回这个实例 →db1接收这个对象。此时状态Database装饰器实例.instance 原始 Database 的实例。阶段 3db2 Database()再次执行Database()再次触发__call__判断self.instance is None→现在不是 None条件不成立直接 return 已经存在的self.instancedb2拿到的是和 db1 完全同一个对象。阶段 4print(db1 is db2)is判断两个变量是不是同一个内存对象输出True。内存关系简图之后 Database → SingletonDecorator的实例 ├ self.cls → 原始Database类 └ self.instance → 第一次new出来的原始Database实例 db1 Database() →得到 self.instance db2 Database() →还是返回 self.instance db1 和 db2 是同一个对象总览简表类型实现方式之后被装饰名字指向什么核心关键点典型场景普通函数装饰器def 外层→def 内层 wrapperwrapper 函数对象调用被装饰函数即调用 wrapper函数日志、计时带参函数装饰器三层 def 嵌套wrapper 函数对象deco(参数)先执行外层拿到真正装饰器可配置的函数增强函数做类装饰器返回新类def 外层内部 class WrapperWrapper 新类实例化得到 Wrapper 外壳对象内部持有原始实例代理类重写类方法类做装饰器(返回新类class 装饰器类实现__call__装饰器类的实例对象类的代码缩进里无法直接用return关键字所以没有办法发挥一个新类所以只停留在实例化的阶段对象()触发__call__可以保存实例状态单例、需要保存状态的装饰器易混区分函数做装饰器装饰器是 def 定义的函数返回函数 / 类类做装饰器装饰器本身是 class的时候生成它的实例依靠__call__实现可调用适合存状态变量self.xxx记忆小口诀装饰函数大多返回 wrapper 函数带参数就套三层。装饰类如果装饰器是函数 →返回一个新包装类如果装饰器是 class →得到它的实例靠__call__干活可以存状态。6. 装饰器的四个魔法方法__init__、__new__、__call__、__getattr__它们统一叫做魔法方法特殊方法依旧属于方法只是 Python 内置、特定时机自动触发obj MyClass()→创建类的实例对象时自动执行__init__obj()→自动执行__call__1.__new__(cls, *args, **kwargs)触发时机类名()最先执行作用真正开辟内存、创建实例空对象特点第一个参数cls 当前类必须 return 创建出来的实例否则__init__不会执行单例模式经常重写此方法2.__init__(self, *args, **kwargs)触发时机类名()创建实例时__new__执行完成生成空对象后自动调用作用实例初始化给实例绑定属性特点第一个参数self 新创建的实例不创建对象只负责赋值初始化class Demo: def __init__(self): print(运行__init__初始化实例) obj Demo() # 触发输出结果运行__init__初始化实例执行顺序Demo()→__new__→__init__3.__call__(self, *args, **kwargs)触发时机实例对象 ()作用让实例支持括号调用obj()自定义调用逻辑通俗解释:一般来说类可以类名()类名()是生成一个实例无论是类还是类的实例调用属性都不用加括号普通实例不能写实例()会报错TypeError: xxx object is not callable。实例.方法名是调用实例方法类.方法名是调用类方法没有实例不加方法名直接加的用法。如果你在类内部定义了def __call__(self, *args, **kwargs) Python 就认定这个实例是可调用对象callable。 有参数时执行obj(a,b)等价自动执行obj.__call__(a,b)没有参数时执行obj()等价自动执行obj.__call__()。若类内部未定义__call__,使用 实例.()就会报错特点没有定义__call__的实例执行obj()直接报错类装饰器SingletonDecorator核心依赖这个方法class Demo: def __call__(self): print(实例被 obj() 调用执行__call__) obj Demo() obj() # 触发 __call__极易混淆区分Demo()→ 调用类 →__new__ __init__造实例obj()→ 调用实例 →__call____call__常见使用场景:类装饰器你正在学习的单例方案把对象做成可调用工具计算器、处理器对象实现简易回调对象补充在 Python 中凡是能xxx()这样加括号执行的东西都是可调用对象callable内部一定实现了__call__。可调用对象包含普通函数def hello(): print(你好) hello()#直接调用函数输出你好普通函数实际是python内置的function类的实例function类自带__call__所以所有 def 函数天然能func()。lambdafn lambda a,b : a*b # fn 是什么 # 和def创建的函数一样属于 function类的实例 print(type(fn)) # class function print(hasattr(fn, __call__)) # True fn(2,3) # 可以直接调用普通函数def直接通过函数名.()调用实际调用的就是内置的_call_匿名函数先用一个变量接收结果再用这个变量() 去调用_call_类类本身是可调用调用产生实例实现了__call__的实例就是本题形式4.__getattr__(self, name)触发时机实例访问不存在的属性 / 方法obj.xxx作用属性找不到时的兜底处理常用来做代理转发你的log_class案例用到特点如果实例本身存在该属性不会触发参数name是你要访问的属性名字符串class Wrapper: def __init__(self, target): self.inner target def __getattr__(self, name): # 转发到内部对象 return getattr(self.inner, name)举例obj.display()类里有 display 方法 → 直接调用不走__getattr__类里没有 display → 进入__getattr__和 getattr、setattr 三兄弟一起记高频组合hasattr(obj, attr)→ 查询有无getattr(obj, attr)→ 获取属性值没有会报错setattr(obj, attr, 值)→ 给对象新增 / 修改属性汇总对比表魔法方法触发语法核心用途__new__类名()最先执行创造实例内存对象__init__类名()new 之后给实例初始化属性__call__实例()让实例可以像函数一样调用__getattr__obj.不存在的属性属性查找失败兜底、代理转发
返回列表