python 装饰器
一、我们在python语言中
常用@classmethod、@staticmethod这个装饰器,装饰器的作用简单来讲就是我们在很多地方可能都会用到一下相同的功能,这时我们会有两种想法。
- 1. 将这部分相同的功能封装成函数,然后在使用这个功能的时候程序中调用这个函数。
- 2. 将这部分相同的功能封装成装饰器,然后在使用这个功能的时候在需要处理的方法前加上对应的装饰器。
这两种方式各有好处,我认为
1中的方式更适合新加功能,通过单独新加函数实现功能,然后在需要的位置使用。
2中的方式更适合固定且经常使用的功能,比如说日志记录,执行时间计算等,只需要在需要用到该功能的时候直接在该方法前加装饰器即可。
二、装饰器
下面我们先看个例子:
- import time
- # Python学习交流群 815624229
- def origin_fun(a, b):
- start_time = time.time()
- s = a + b
- exec_time = time.time() - start_time
- print('执行时间: {}'.format(exec_time))
- return s
以上是原函数,我们要得到传入两个参数的加和操作的时间消耗,我们如果需要对很多操作进行时间记录,有两个方法
1、拆出函数实现
- def func(a, b):
- return a+b
- def get_begin_date():
- return time.time()
- def final_func(a, b):
- start_time = get_begin_date()
- s = func(a, b)
- exec_time = get_begin_date() - start_time
- print('执行时间: {}'.format(exec_time))
- return s
可以发现我们这样是可以实现的, 但是这样再使用的时候还是有点繁琐,接下来我们用装饰器实现
2、装饰器实现
(1)我们先来介绍一下装饰器的一个结构:
简单理解就是将添加了装饰器的函数(func)传入装饰器函数(decorator)中,在装饰器函数中创建内置函数(wrapper),内置函数实现通用的功能并真正执行传入的函数(func)之后将返执行的函数结果在内置函数(wrapper)中返回,在装饰器函数(decorator)中将装饰器的内置函数(wrapper)作为返回对象返回
- def decorator(func):
- def wrapper(*args, **kwargs):
- """
- define args and kwargs
- """
- # 通用的功能,这里我们认为输出传入的函数名称就是通用功能
- print(func.__name__)
- # 执行传入的方法
- return func(*args, **kwargs)
- # 返回当前方法的内置函数
- return wrapper
- @decorator
- def func(a, b):
- return a + b
这么一看的话大家应该就会觉得装饰器也很好理解了。
(2)下面我们就将上述的例子通过装饰器实现
a. 无参装饰器
- def time_local(func):
- def wrapper(*args, **kwargs):
- start_time = time.time()
- f = func(*args, **kwargs)
- exec_time = time.time() - start_time
- print('执行时间: {}'.format(exec_time))
- return f
- return wrapper
- @time_local
- def func1(a, b):
- return a+b
- @time_local
- def func2(a, b):
- return a*b
上述代码块就是我们的装饰器实现,可以发现我们在计算其他操作的消耗的时间的时候只需要在函数前添加装饰器就可以了,这样就代码的改动就会尽可能少的改动,这就是简单的装饰器的实现方式和应用方式。
b. 有参装饰器
高级一点的我们可能需要对装饰器进行传参,这样的装饰器就是在无参装饰器外再包一层函数,如下
- def time_local_args(level):
- def time_local(func):
- def wrapper(*args, **kwargs):
- if level:
- start_time = time.time()
- f = func(*args, **kwargs)
- exec_time = time.time() - start_time
- print('执行时间: {}'.format(exec_time))
- else:
- f = func(*args, **kwargs)
- return f
- return wrapper
- return time_local
- @time_local_args(level=True)
- def func(a, b):
- return a+b
这样一看的话就很清晰了吧,装饰器就是一个函数内嵌套一个内置函数,在内置函数中实现通用功能以及执行被装饰的函数,嵌套函数的返回值为被装饰的函数,装饰器函数返回内置函数对象。
c. 再优雅一些的写法, 传参并封装成类且有参数的装饰器
- class TimeLocal:
- def __init__(self, level):
- self._level = level
- def __call__(self, func, *args, **kwargs):
- def wrapper(*args, **kwargs):
- if self._level:
- start_time = time.time()
- f = func(*args, **kwargs)
- exec_time = time.time() - start_time
- print('执行时间: {}'.format(exec_time))
- else:
- f = func(*args, **kwargs)
- return f
- return wrapper
- @TimeLocal(level=True)
- def func4(a, b):
- return b / a
d.偏函数与类结合构建装饰器
事实上,Python 对某个对象是否能通过装饰器( @decorator)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象。
对于这个 callable 对象,我们最熟悉的就是函数了。
除函数之外,类也可以是 callable 对象,只要实现了call 函数(上面几个例子已经接触过了)。
还有容易被人忽略的偏函数其实也是 callable 对象。
接下来就来说说,如何使用 类和偏函数结合实现一个与众不同的装饰器。
如下所示,DelayFunc 是一个实现了 call 的类,delay 返回一个偏函数,在这里 delay 就可以做为一个装饰器。(以下代码摘自 Python工匠:使用装饰器的小技巧)
- import time
- import functools
- class DelayFunc:
- def __init__(self, duration, func):
- self.duration = duration
- self.func = func
- def __call__(self, *args, **kwargs):
- print(f'Wait for {self.duration} seconds...')
- time.sleep(self.duration)
- return self.func(*args, **kwargs)
- def eager_call(self, *args, **kwargs):
- print('Call without delay')
- return self.func(*args, **kwargs)
- def delay(duration):
- """
- 装饰器:推迟某个函数的执行。
- 同时提供 .eager_call 方法立即执行
- """
- # 此处为了避免定义额外函数,
- # 直接使用 functools.partial 帮助构造 DelayFunc 实例
- return functools.partial(DelayFunc, duration)
- @delay(duration=2)
- def add(a, b):
- return a+b
以上就是我们装饰器的理解以及几张实现的方式,最后我们看一下用在类上的装饰器的代码
- instances = {}
- def singleton(cls):
- def get_instance(*args, **kw):
- cls_name = cls.__name__
- print('===== 1 ====')
- if not cls_name in instances:
- print('===== 2 ====')
- instance = cls(*args, **kw)
- instances[cls_name] = instance
- return instances[cls_name]
- return get_instance
- @singleton
- class User:
- _instance = None
- def __init__(self, name):
- print('===== 3 ====')
- self.name = name
- print(User('sdma'))
- >>> ===== 1 ====
- >>> ===== 2 ====
- >>> ===== 3 ====
- >>> <__main__.User object at 0x000001EAC67AEC48>