Python装饰器

python中的语法糖, 装饰器, 需要先搞清楚闭包和函数参数解包

装饰器

装饰器要装饰一个对象, 可以是函数, 可以是类, 装饰器本身可以是函数也可以是类

装饰

  • 能够接受一个对象
  • 要有对对象的修改或利用对象的能力
  • 对象的基本功能不能变(装饰不应该改变我们写出的直观的功能)
  • 装饰器可能还需要我们能够进行一定的控制, 它可能会需要参数
  • 如果我们愿意, 不应该只能装饰一次

一个例子

一个函数装饰函数的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import time

def de(func, *args, **kwargs):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
print('%.3f s' % (time.time() - start))
return result
return wrapper

@de
def f():
time.sleep(3.123)
print("Hi...")
# 上述函数定义等价于f = de(f)

f()

Hi...
3.124 s

能够接受一个对象

函数当然可以接受一个函数作为参数

要有对对象的修改或利用对象的能力

我们当然可以在装饰器内做些事情, 比如计算运行时间

这还是可以应在任意函数的装饰器, 我们还可以为特定的函数编写装饰器, 比如flask将视图函数与路由连接起来

对象的基本功能不能变

正如我们看到的,

  • 将所有的参数获取并传给原来的函数
  • 我们返回一个函数

什么也不做的装饰器

1
2
3
4
5
6
7
8
def de(func, *args, **kwargs):
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
return wrapper

# 甚至是这样
def de(func):
return func

我们需要参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 需要嵌套更多
def de2(s):
def de(func):
def wrapper(*args, **kwargs):
start = time.time()
time.sleep(s)
result = func(*args, **kwargs)
print('%.3f s' % (time.time() - start))
return result
return wrapper
return de

@de2(2)
def f2():
print("Hi.....")
# 相当于 f2 = de2(s)(f2)

f2()

Hi.....
2.001 s

f2 = de2(s)(f2)

de2(s)返回了一个函数de, 即de2(s)=de, 即我们没有参数时的装饰器
de(f2), 返回了我们的函数

应该记住的

最后返回的是最内层的wrapper函数, wrapper外部的函数都已执行
所以我们可以看作装饰有两部分, 一部分wrapper函数外部代码, 定义函数时即进行装饰,
另一部分是wrapper内部代码, 调用时进行装饰

不仅要原函数的返回值

1
2
3
4
5
6
7
8
9
10
def de2(s):
def de(func):
def wrapper(*args, **kwargs):
start = time.time()
time.sleep(s)
result = func(*args, **kwargs)
print('%.3f s' % (time.time() - start))
return result
return wrapper, time.time() - start
return de

多个装饰器

离函数最近的装饰器先装饰,然后外面的装饰器再进行装饰
我们最好不要写……会晕的

类装饰器

类装饰器依赖的是类的__call__方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class De():
def __init__(self, func):
self.s = "Hi!"
self.func = func

def __call__(self, *args, **kwargs):
print(self.s)
return self.func(*args, **kwargs)


@De
def foo(s):
print(s)
# 相当于foo = De(foo).__call__

foo("Hi...")

Hi!
Hi...

使用场景

通用装饰器

就像例子中的装饰器, 用在那个函数上都可以

  • 日志
  • 运行时间

其他

  • 将函数余其他东西关联,
    • url和view
    • django注册应用
    • 注册任务
  • 原函数调用前检查
    • 权限验证
    • 类型检查
    • 输出格式化
  • 原函数调用时检查
    • 异常捕捉
  • 函数调用后的一些处理

注意

原函数的属性会变

例, 函数也是个对象, 它的属性__name__为函数名, 但是装饰器工作后会改变它的属性, 按照我们的等价写法来理解, 这很正常.
解决
引起我们函数名改变的原因就是wrapper的调用, 所以我们可以手动修改wrapper.__name__=func.__name__
更好的方法是使用functools库的wraps装饰器

1
2
3
@functools.wraps(func):
def wrapper(*arg, **kwargs):
...

这样使用装饰器不会有任何副作用

模板

有时我们直接记住一些模板就好了, 甚至我们都不会取写装饰器, 最少应该在自己使用时了解它工作的过程

函数装饰函数

1
2
3
4
5
6
7
8
9
def de(cls):
...
def wrapper(*arg, **kwargs):
...
result = cls(*arg, **kwargs)
...
return result[, ...]
...
return wrapper

函数装饰类

1
2
3
4
5
6
7
8
9
def de(cls):
...
def wrapper(*arg, **kwargs):
...
instance = cls(*arg, **kwargs)
...
return instance[, ...]
...
return wrapper

类装饰函数

1
2
3
4
5
6
7
8
9
10
11
12
class De():
def __init__(self, func):
self.func = func
...

...

def __call__(self, *args, **kwargs):
...
result = self.func(*args, **kwargs)
...
return result[, ...]

类装饰类

1
2
3
4
5
6
7
8
9
10
11
12
class De():
def __init__(self, cls):
self.cls = cls
...

...

def __call__(self, *args, **kwargs):
...
instance = self.cls(*args, **kwargs)
...
return instance[, ...]
文章作者: Shoor
文章链接: https://shoorday.github.io/posts/f2b4268f/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Shoor's Blog