Python 装饰器
2021/7/11 1:05:43
本文主要是介绍Python 装饰器,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
Python 装饰器
学习就是要力求知其然,亦知其所以然
什么是装饰器?
python 中的装饰器有什么作用呢?
- 面向切面编程(AOP)
- 原有基础上增加额外的功能
上面说的作用,我们能想象到可以用来做,打印日志、事务处理、缓存、权限校验等功能。
装饰器类型
- 简单装饰器
- 带参数装饰器
- 类装饰器
- 内置装饰器
简单装饰器
需求:需要对某些函数调用前输出 “xx函数被调用用了”的日志
def foo(): print('i am foo') def bar(): print("i am bar") def gin(): print("i am gin")
实现:
-
直接在函数中添加 “xx 函数被调用了”
def foo(): print('foo is called') print('i am foo')
- 扩展性差,需改信息的时候,每个函数都需要改动
-
装饰器
# 定义一个装饰器 def logging(func): def wrapper(*args, **kwargs): print('%s is called' % func.__name__) return func(*args, **kwargs) return wrapper
# logging 装饰器修饰 gin() 函数 @logging def gin(): print("i am gin")
我们调用一下 gin()
gin is called i am gin
输出结果能够实现我们的需求,这个非常好,但是它的原理是什么呢??
原理
先说结果,这个和 @ 语法糖有关,@logging 放置在 gin() 函数上 其实就是等于 gin = logging(gin)
接下我们来验证一下 @ 语法糖是否是这样的
# foo 被包装成 logging.wrapper 类型 foo = logging(foo) foo()
输出的结果和添加了 @logging 装饰器的一致。
带参数装饰器
需求:现在需求升级了,需要自定义打印不同的等级的日志。
我们先,能否在装饰器上加多个参数,表示等级呢? 答案是肯定的,而且越来越有意思了。
# 带等级的日志装饰器 def advanced_log(level): def decorate(func): def wrapper(*args, **kwargs): if level == 'warn': print('%s: %s is called' % (level, func.__name__)) elif level == 'info': print('%s: %s is called' % (level, func.__name__)) else: print('None: %s is called' % func.__name__) return func(*args, **kwargs) return wrapper return decorate @advanced_log("warn") def vag(): print("i am vag") vag()
输出的结果
warn: vag is called i am vag
符合我们的需求。
原理
结果的是出来的,但是可能对它的原理还是很模糊。其实这里还是 @ 的语法糖,我们转换一下写法就清晰了。带参数装饰器 @advanced_log("warn") 修饰 vag() 函数就相当于 vag = advanced_log('info')(vag)
我们先验证一下这个结论再来分析
def goo(): print("i am goo") goo = advanced_log('warn')(goo) goo()
warn: goo is called i am goo
与装饰器输出的结果相同。
我们结合 advanced_log()
函数 分析一下 goo = advanced_log('warn')(goo)
advanced_log('warn')
返回的是advanced_log()
中的函数 decorate(),所以简化成为goo = decorate(goo)
goo = decorate(goo)
, 这时就转换成为无参的装饰器了,又因为有参数warn
,可以确定方法需要执行哪个分支- 在调用 goo() 函数时,
decorate(goo)
被执行,转化成decorate(goo)()
,而decorate(goo)
返回的是wrapper
,所以简化成wrapper()
- 继续
wrapper()
被执行输出日志(我们需要额外处理的逻辑) - 最后才调用的原始函数
goo()
小结
在这里我们小结一下,不难发现,python 能用装饰器的本质是 函数可以当做参数!!,而装饰器的本质是函数的包装。
小坑
我们仔细看一下第一个装饰器, logging 的函数,不难发现被装饰的函数被另一个函数 wrapper 包装起来了,返回的是 wrapper,然后 @logging 装饰器再将 wrapper 赋值给被装饰的函数。
这样就产生了一个问题,被装饰的函数还是原来的函数吗?
其实答案是否定的,因为都被 wrapper 替换。这样可以不行,如果被修饰的函数 gin 中有文档说明,或者某些判断需要依赖函数的__name__
属性,被替换就乱了套了,所以官方出了一个新的装饰器 @wraps
,起作用是将被装饰的函数的属性,如函数名__name__
、__doc__
等信息复制到被包装的函数 wrapper
中。
验证不添加 @wraps
的情况
def logg(func): #@wraps(func) def wrapper(*args, **kwargs): print(func.__name__, 'was called') return func(*args, **kwargs) return wrapper def yog(x): """does some thing""" print('i am yog')
wrapper None
这可不是我们想要的!
验证添加 @wraps
的情况
去掉 #@wraps(func)
注释 =>@wraps(func)
yog does some thing
输出正常!!
@wraps 也是一个带参数的装饰器这里就不展开了
类装饰器
类装饰器依靠内部的是类内部 __call__()
方法,当使用 @ 语法糖,将类作为装饰器附加到函数上,当函数被调用,就会转换成调用类的 __call__()
方法
假设有类 Koo,存在 Koo() 等于
Koo.__call__()
,()
代表了可调用,如果类名 + "()" 代表调用类中的__call__()
方法
class Koo(object): def __init__(self, func): self._fun = func def __call__(self, *args, **kwargs): print('class decorator starting') self._fun() print('class decorator ending') @Koo def sar(): print('I am sar') sar() print(sar)
class decorator starting I am sar class decorator ending <__main__.Koo object at 0x0000020B7FD8B310>
原理
类装饰器 @Koo
修饰的函数 sar
,在函数 sar()
被调用时,该函数会被当做该类的初始化参数传入,并且该函数变量会执行该对象,即 sar = Koo(sar)
变量 sar 从指向函数,到指向对象。其实和第一种的简单修饰思路差不多,只不过一个返回的是函数包装,个返回的是对象包装。
内置装饰器
- 先了解
__get__
特性 和__set__
(必须)- 实例方法,第一个参数 self,可以被实例所调用
- 类方法是第一个参数为 cls ,可以被实例或者类直接调用
- 静态方法,参数无要求,可以被实例和类直接调用
@classmethod
@classmethod
的作用就是让一个方法变成类方法
class Hi(object): def __init__(self, value): self.value = value @classmethod def say_hi(cls): print("hello world!") def say_goodbye(self): print(self.value, "goodbye") Hi.say_hi()
输出结果
hello world!
原理
@classmethod
用到的是 __get__
和装饰器的知识,现在我们自定义一个类方法装饰器 ClassMethod
# 自定义的 类方法装饰器 class ClassMethod(object): def __init__(self, func): self.func = func # instance 是对象, owner 是类 def __get__(self, instance, owner): def wrapper(*args, **kwargs): return self.func(owner, *args, **kwargs) return wrapper class Hi(object): def __init__(self, value): self.value = value @classmethod def say_hi(cls): print(cls) print("hello world!") # say_goodbye = ClassMethod(say_goodbye) # 这里故意写成 owner 为参数名是想 ClassMethod 中 __get__ 方法中的 owner 对应 方便理解 @ClassMethod def say_goodbye(owner): print(owner) print("goodbye") Hi.say_hi() Hi.say_goodbye()
输出结果
<class '__main__.Hi'> hello world! <class '__main__.Hi'> goodbye
我们自己实现了一个类方法装饰器,原理看得非常透彻,我们归纳一下,其实 @ClassMethod
装饰在say_goodbye
方法,相当于say_goodbye = ClassMethod(say_goodbye)
, say_goodbye
指向了ClassMethod
类型对象,又根据 ClassMethod
类中定义了 __get__
,调用Hi.say_goodbye()
相当于调用 __get__
里面的包装的方法。
@staticmethod
我们用一个例子想感受一下静态方法
class Coco(object): @staticmethod def static_co(): print('i am static method') coco = Coco() Coco.static_co() coco.static_co()
输出
i am static method i am static method
原理
@staticmethod
的原理与 @classmethod
的原理一样,我们实现自己的静态方法装饰器
# 自定义的 静态方法装饰器 class StaticMethod(object): def __init__(self, func): self.func = func def __get__(self, instance, owner): def wrapper(*args, **kwargs): # 与类方法的区别是是否需要传入 owner return self.func(*args, **kwargs) return wrapper class Coco(object): @staticmethod def static_co(): print('i am static method co') @StaticMethod def static_coco(): print('i am static method coco') coco = Coco() Coco.static_co() coco.static_co() Coco.static_coco() coco.static_coco()
输出结果
i am static method co i am static method co i am static method coco i am static method coco
原理基本与 @classmethod
一样,唯一的区别是在定义包装原有方法的时候是否传入 ower
即类对象。
@property
对象调用方法可以想调用属性一样。
class Person(object): def __init__(self, name): self._name = name @property def my_name(self): return self._name @my_name.setter def my_name(self, name): self._name = name return self._name p = Person('gin') print(p.my_name) # p.name() p.my_name = 'yyy' # p.set_name('yyy') print(p.my_name)
gin yyy
结果也验证了通过@property装饰的方法,可以像属性一样调用。
原理
通过手写一个自定义的属性装饰器 @Property
(主要大写)
class Property(object): def __init__(self, fget=None, fset=None): self.fget = fget self.fset = fset def __get__(self, instance, owner): if instance is not None: return self.fget(instance) return self def __set__(self, instance, value): self.fset(instance, value) def setter(self, func): self.fset = func # 更新属性 return self class Person(object): def __init__(self, name, age): self._name = name self._age = age @property def my_name(self): return self._name @my_name.setter def my_name(self, name): self._name = name return self._name # age = Property(age) @Property def age(self): return self._age # Property(age).setter @age.setter def age(self, age): self._age = age return self._age p = Person('gin', 18) print(p.my_name) # p.name() print(p.age) p.my_name = 'yyy' # p.set_name('yyy') p.age = 19 print(p.my_name) print(p.age)
gin 18 yyy 19
结果和原有的属性装饰器功能一致。@Property
的原理总体和 @classmethod
和 @staticmethod
思路是一直的都是利用了 __get__
和 __set__
等方法。
总结
装饰器语法解析就是被装饰的函数或者方法 test = 装饰器 ( test )
下面例子会解析成 test = C(B(A(test)))
@A @B @C def test(): pass
参考
https://www.cnblogs.com/rxybk/p/13284852.html
https://blog.csdn.net/weixin_30432179/article/details/99093631
代码
# 装饰器 # 函数装饰器 # 简单装饰器 from functools import wraps # 在方法调用前输出 xxx 被调用了 def logging(func): def wrapper(*args, **kwargs): print('%s is called' % func.__name__) return func(*args, **kwargs) return wrapper def foo(): print('foo is called') print('i am foo') @logging def gin(): print("i am gin") def bar(): print("i am bar") # foo 被包装成 logging.wrapper 类型 foo = logging(foo) # 验证数据 bar() # 重点 # @ 语法糖 @logging <==> gin = logging(gin) gin() foo() print("-----------------------------分割线--------------------") # 带参数装饰器 高级 def advanced_log(level): def decorate(func): def wrapper(*args, **kwargs): if level == 'warn': print('%s: %s is called' % (level, func.__name__)) elif level == 'info': print('%s: %s is called' % (level, func.__name__)) else: print('None: %s is called' % func.__name__) return func(*args, **kwargs) return wrapper return decorate @advanced_log("warn") def vag(): print("i am vag") def goo(): print("i am goo") # 验证数据 # 重点 # @ 语法糖 @advanced_log("warn") <==> yan = advanced_log('warn')(yan) vag() goo = advanced_log('warn')(goo) goo() # 总结 可以用装饰器这种方式 本质是 函数可以当做参数传递 print('-------------------------------分割线----------------') # 问题: 装饰器 是包装原有的函数,这样会导致一个问题,就是原函数的原信息不见了,比如 # docstring、__name__、参数列表等 # functools.wraps 解决 原有函数元信息被修改的问题 # 原理 wraps 本质也是一个装饰器,它把原函数的原信息拷贝到装饰器函数中,这样装饰器函数就和原有的函数的原信息一致了 def logg(func): @wraps(func) def wrapper(*args, **kwargs): print(func.__name__, 'was called') return func(*args, **kwargs) return wrapper @logg def yog(): """does some thing""" print('i am yog') # yog print(yog.__name__) # does some thing print(yog.__doc__) print('-----------------------分割线-------------------------------') # 类装饰器 :还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法 class Koo(object): def __init__(self, func): self._fun = func def __call__(self, *args, **kwargs): print('class decorator starting') self._fun() print('class decorator ending') # 重点: 类装饰器 @Koo <==> sar = Koo(sar)() @Koo def sar(): print('I am sar') # sar = Koo(sar) sar() print(sar) # 分割线 print("---------------分割线--------------------") # 内置装饰器 # 自定义的 类方法装饰器 class ClassMethod(object): def __init__(self, func): self.func = func # instance 是对象, owner 是类 def __get__(self, instance, owner): def wrapper(*args, **kwargs): return self.func(owner, *args, **kwargs) return wrapper class Hi(object): def __init__(self, value): self.value = value @classmethod def say_hi(cls): print(cls) print("hello world!") # say_goodbye = ClassMethod(say_goodbye) # 这里故意写成 owner 为参数名是想 ClassMethod 中 __get__ 方法中的 owner 对应 方便理解 @ClassMethod def say_goodbye(owner): print(owner) print("goodbye") Hi.say_hi() Hi.say_goodbye() # 静态方法 # 自定义的 静态方法装饰器 class StaticMethod(object): def __init__(self, func): self.func = func def __get__(self, instance, owner): def wrapper(*args, **kwargs): # 与类方法的区别是是否需要传入 owner return self.func(*args, **kwargs) return wrapper class Coco(object): @staticmethod def static_co(): print('i am static method co') @StaticMethod def static_coco(): print('i am static method coco') coco = Coco() Coco.static_co() coco.static_co() Coco.static_coco() coco.static_coco() # 属性装饰器 class Property(object): def __init__(self, fget=None, fset=None): self.fget = fget self.fset = fset def __get__(self, instance, owner): if instance is not None: return self.fget(instance) return self def __set__(self, instance, value): self.fset(instance, value) def setter(self, func): self.fset = func # 更新属性 return self class Person(object): def __init__(self, name, age): self._name = name self._age = age @property def my_name(self): return self._name @my_name.setter def my_name(self, name): self._name = name return self._name # age = Property(age) @Property def age(self): return self._age # Property(age).setter @age.setter def age(self, age): self._age = age return self._age p = Person('gin', 18) print(p.my_name) # p.name() print(p.age) p.my_name = 'yyy' # p.set_name('yyy') p.age = 19 print(p.my_name) print(p.age)
这篇关于Python 装饰器的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-24Python编程基础详解
- 2024-11-21Python编程基础教程
- 2024-11-20Python编程基础与实践
- 2024-11-20Python编程基础与高级应用
- 2024-11-19Python 基础编程教程
- 2024-11-19Python基础入门教程
- 2024-11-17在FastAPI项目中添加一个生产级别的数据库——本地环境搭建指南
- 2024-11-16`PyMuPDF4LLM`:提取PDF数据的神器
- 2024-11-16四种数据科学Web界面框架快速对比:Rio、Reflex、Streamlit和Plotly Dash
- 2024-11-14获取参数学习:Python编程入门教程