学习就是要力求知其然,亦知其所以然
python 中的装饰器有什么作用呢?
上面说的作用,我们能想象到可以用来做,打印日志、事务处理、缓存、权限校验等功能。
需求:需要对某些函数调用前输出 “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
,可以确定方法需要执行哪个分支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
的作用就是让一个方法变成类方法
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__
里面的包装的方法。
我们用一个例子想感受一下静态方法
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
即类对象。
对象调用方法可以想调用属性一样。
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)
原文:https://www.cnblogs.com/gingo/p/14995411.html