- 一步步从简到繁学习装饰器用法
- 其他一些装饰器实例
- Python中自带的装饰器
按照惯例,先上代码:GitHub - xianhu/LearnPython: 以撸代码的形式学习Python
一步步从简到繁学习装饰器用法
(1)简单的装饰器,实现日志输出功能:
# 构建装饰器
def logging(func):
@functools.wraps(func)
def decorator():
print("%s called" % func.__name__)
result = func()
print("%s end" % func.__name__)
return result
return decorator
# 使用装饰器
@logging
def test01():
return 1
# 测试用例
print(test01())
print(test01.__name__)
代码很简单,很容易看懂。这里注意"functools.wraps"用法,其目的是"test01.__name__"输出正确的"test01"。"@logging"相当于"test01 = logging(test01)",返回的是decorator函数,所以如果不加"functools.wraps",则"test01.__name__"返回为"decorator"。
注意,此时test01没有参数,对于带有参数的函数,logging装饰器则不再适用。那么如果想装饰带有参数的函数,装饰器该怎么写呢?
(2)装饰器传入函数参数,并正确返回结果:
# 构建装饰器
def logging(func):
@functools.wraps(func)
def decorator(a, b):
print("%s called" % func.__name__)
result = func(a, b)
print("%s end" % func.__name__)
return result
return decorator
# 使用装饰器
@logging
def test01(a, b):
print("in function test01, a=%s, b=%s" % (a, b))
return 1
# 测试用例
print(test01(1, 2))
这里的test01函数带有参数a、b,那么decorator函数带有同样的参数即可。那么问题又来了,如何让logging装饰器更加通用,而不是只装饰参数为两个的函数呢?这时候自然想到Python中的 * 和 ** 的用法。
# 构建装饰器
def logging(func):
@functools.wraps(func)
def decorator(*args, **kwargs):
print("%s called" % func.__name__)
result = func(*args, **kwargs)
print("%s end" % func.__name__)
return result
return decorator
# 使用装饰器
@logging
def test01(a, b):
print("in function test01, a=%s, b=%s" % (a, b))
return 1
# 使用装饰器
@logging
def test02(a, b, c=1):
print("in function test02, a=%s, b=%s, c=%s" % (a, b, c))
return 1
# 测试用例
print(test01(1, 2))
print(test02(1, 2, c=3, d=4))
此时对于任意参数的函数,logging都可以进行装饰。但是注意,logging装饰器是不带参数的,那么装饰器可以带参数吗?当然可以,我们换个例子:参数检查。
(3)构建带有参数的装饰器,并正确返回结果:
# 构建装饰器
def params_chack(a_type, b_type):
def _outer(func):
@functools.wraps(func)
def _inner(a, b):
assert isinstance(a, a_type) and isinstance(b, b_type)
return func(a, b)
return _inner
return _outer
# 使用装饰器
@params_chack(int, (list, tuple))
def test03(a, b):
print("in function test03, a=%s, b=%s" % (a, b))
return 1
# 测试用例
print(test03(1, [2, 3])) # 参数正确
print(test03(1, 2)) # 参数错误
从代码可以看出,实际上就是在原有装饰器的基础上,外层又加了一层包装。params_check装饰器的作用是限制个参数为a_type,第二个参数为b_type。类似于(2),这里如何让装饰器更加通用,而不是只装饰参数为两个的函数呢?这里又一次想到Python中的 * 和 **。
# 构建装饰器
def params_chack(**, **kw*):
def _outer(func):
@functools.wraps(func)
def _inner(*args, **kwargs):
result = [isinstance(_param, _type) for _param, _type in zip(args, *)]
assert all(result), "params_chack: invalid parameters"
result = [isinstance(kwargs[_param], kw*[_param]) for _param in kwargs if _param in kw*]
assert all(result), "params_chack: invalid parameters"
return func(*args, **kwargs)
return _inner
return _outer
# 使用装饰器
@params_chack(int, str, c=(int, str))
def test04(a, b, c):
print("in function test04, a=%s, b=%s, c=%s" % (a, b, c))
return 1
# 测试用例
print(test04(1, "str", 1)) # 参数正确
print(test04(1, "str", "abc")) # 参数正确
print(test04("str", 1, "abc")) # 参数错误
此时params_check装饰器不但能够传入任意个数的参数,而且支持K-V形式的参数传递。
(4)使用装饰器装饰类中的函数,比较简单,直接看代码。注意此时个参数为self本身:
# 使用装饰器
class ATest(object):
@params_chack(object, int, str)
def test(self, a, b):
print("in function test of ATest, a=%s, b=%s" % (a, b))
return 1
# 测试用例
a_test = ATest()
a_test.test(1, "str") # 参数正确
a_test.test("str", 1) # 参数错误
(5)多个装饰器同时装饰一个函数,也比较简单,直接看代码:
# 使用装饰器
@logging
@params_chack(int, str, (list, tuple))
def test05(a, b, c):
print("in function test05, a=%s, b=%s, c=%s" % (a, b, c))
return 1
# 测试用例
print(test05(1, "str", [1, 2])) # 参数正确
print(test05(1, "str", (1, 2))) # 参数正确
print(test05(1, "str", "str1str2")) # 参数错误
(6)将装饰器写为类的形式,即“装饰器类”。此时对于装饰器类的要求是必须是可被调用的,即必须实现类的__call__方法。直接上代码:
# 构建装饰器类
class Decorator(object):
def __init__(self, func):
self.func = func
return
def __call__(self, *args, **kwargs):
print("%s called" % self.func.__name__)
result = self.func(*args, **kwargs)
print("%s end" % self.func.__name__)
return result
# 使用装饰器
@Decorator
def test06(a, b, c):
print("in function test06, a=%s, b=%s, c=%s" % (a, b, c))
return 1
# 测试用例
print(test06(1, 2, 3))
# 构建装饰器类
class ParamCheck(object):
def __init__(self, **, **kw*):
self.* = *
self.kw* = kw*
return
def __call__(self, func):
@functools.wraps(func)
def _inner(*args, **kwargs):
result = [isinstance(_param, _type) for _param, _type in zip(args, self.*)]
assert all(result), "params_chack: invalid parameters"
result = [isinstance(kwargs[_param], self.kw*[_param]) for _param in kwargs if _param in self.kw*]
assert all(result), "params_chack: invalid parameters"
return func(*args, **kwargs)
return _inner
# 使用装饰器
@ParamCheck(int, str, (list, tuple))
def test07(a, b, c):
print("in function test06, a=%s, b=%s, c=%s" % (a, b, c))
return 1
# 测试用例
print(test07(1, "str", [1, 2])) # 参数正确
print(test07(1, "str", (1, 2))) # 参数正确
print(test07(1, 2, (1, 2))) # 参数错误
其他一些装饰器实例
函数缓存:一个函数的执行结果可以被缓存在内存中,下次再次调用时,可以先查看缓存中是否存在,如果存在则直接返回缓存中的结果,否则返回函数调用结果。这种装饰器比较适合装饰过程比较复杂或耗时的函数,比如数据库查询等。
# 实例: 函数缓存
def funccache(func):
cache = {}
@functools.wraps(func)
def _inner(*args):
if args not in cache:
cache[args] = func(*args)
return cache[args]
return _inner
# 使用装饰器
@funccache
def test08(a, b, c):
# 其他复杂或耗时计算
return a + b + c
还有很多其他例子,比如函数调用计数、函数计时、函数自动重试等,思路都基本相同,这里就不一一列举了。
Python中自带的装饰器
Python中自带有三个和class相关的装饰器:@staticmethod、@classmethod 和@property。
(1)先看@property,可以将其理解为“将类方法转化为类属性的装饰器”。先看实例:
# 使用Python自带的装饰器
class People(object):
def __init__(self):
self._name = None
self._age = None
return
@property
def name(self):
return self._name
@name.setter
def name(self, name):
self._name = name
return
@property
def age(self):
return self._age
@age.setter
def age(self, age):
assert < age < 120
self._age = age
return
p = People()
p.name = "tom" # 设置name
p.age = 12 # 设置age
print(p.name, p.age) # 输出name和age
p.age = 120 # 设置age, 此时认为120为异常数据
这里定义一个People类,有两个属性name和age。当我们声明了实例p,使用p操作name和age时,实际上是调用的name、age方法,此时会做参数检查等工作。@property将name方法转化为属性,同时当对该属性进行赋值时,会自动调用@name.setter将下边的name方法。
@property有.setter、.getter和.deleter三中装饰器,分别对应赋值、取值和删除三种操作。
(2)@staticmethod 将类成员方法声明为类静态方法,类静态方法没有 self 参数,可以通过类名或类实例调用。
(3)@classmethod 将类成员方法声明为类方法,类方法所接收的个参数不是self,而是cls,即当前类的具体类型。
静态方法和类方法都比较简单,一个简单的例子解释静态方法和类方法:
# 类静态方法和类方法
class A(object):
var = 1
def func(self):
print(self.var)
return
@staticmethod
def static_func():
print(A.var)
return
@classmethod
def class_func(cls):
print(cls.var)
cls().func()
return
=============================================================
作者主页:笑虎(Python爱好者,关注爬虫、数据分析、数据挖掘、数据可视化等)
作者专栏主页:撸代码,学知识 - 知乎专栏
作者GitHub主页:撸代码,学知识 - GitHub
欢迎大家拍砖、提意见。相互交流,共同进步!
==============================================================