Python 如何实现装饰器?

Python 如何实现一个装饰器

首先,装饰器为调用高阶函数提供了一种简单的语法
根据定义,装饰器是一个函数,它接受另外一个函数并在其基础功能上做统一的扩展

闭包

对于 Python 来说,一切皆对象,对象也是可以作为参数传递,这就是装饰器实现的基础

例如,我想实现一个日志打印功能,调用对应方法或者对象时,可以显示其运行的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
def printer(func):
print(f"func {func.__name__} running", )
func()
print(f"func {func.__name__} ended")


def task():
print("task run")


if __name__ == '__main__':
printer(task)

在这里我们实现了task 方法,在调用这个方法的时候,会显示调用的相关信息。

可以看到我们使用printer 这个功能的时候,都是采用printer(func)的方式,如果在多处使用的话,
那么有没有比较简单的方式,例如在执行task方法的时候,自动去触发printer呢,还能像调用原方法一样?

于是, 我们可以将该方法当成一个参数传递后,返回一个新的对象调用的方式:

1
2
3
4
5
6
7
8
9
10
11
12
def printer(func):
def inner():
print(f"func {func.__name__} running", )
func()
print(f"func {func.__name__} ended")
return inner

def task():
print("task run")

task = printer(task)
task()

Python 中允许在一个方法中嵌套另外一个方法,这种特殊的机制就是闭包
这个内部方法可以保留外部方法的作用域,内部方法也可以访问到外部方法的参数和变量。

装饰器

python 支持一种装饰器语法糖「@」,使用这个语法糖,我们也可以实现与上面完全相同的功能

1
2
3
@printer
def task():
print("task run ")

functools.wraps

现在我们已经知道,装饰器其实就是先定义好一个闭包,然后使用语法糖@来装饰方法,最后达到重新定义方法的作用
但是这样使用的方法其实最终执行的是 inner 方法

like:

1
2
3
4
5
6
7
@printer
def task():
print("task run ")

print(task.__name__)

# Output: inner

如何解决这个问题?

python 其实有内置的 functools 模块,提供了一个 wraps 方法,专门来解决这个问题。

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


def logger(func):
@wraps(func)
def inner():
print(f"func {func.__name__} running", )
func()
print(f"func {func.__name__} ended")
return inner


@logger
def task():
print("task run ")

print(task.__name__)

# output:
# hello

带参数的装饰器

装饰器当然也可以传入参数,例如:*args**kwargs
例如使用装饰器来计算程序的运行时间:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# coding: utf8

import time
from functools import wraps

def timeit(prefix):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
func(*args, **kwargs)
end = time.time()
print('%s: duration time: %ds' % (prefix, int(end - start)))
return wrapper
return decorator

@timeit('prefix1')
def hello(name):
time.sleep(1)
print('hello %s' % name)

装饰器模版

1
2
3
4
5
6
7
8
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
# do something
value = func(*args, **kwargs)
return value
return wrapper

类实现装饰器

__call__ 魔法方法结合类也可以实现装饰器

这里也给出一个通用模版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import functools
class Decorator(object):
def __int__(self, params):
self.params = params

def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# do something
func(*args, **kwargs)
return wrapper

@Decorator('params')
def hello():
print("hello")


hello()

装饰器的使用场景

记录调用日志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from functools import wraps
import logging


class Logger(object):
def __init__(self, name):
self.name = name

def __call__(self, func):
@wraps(func)
def wrapper(*args, **kwargs):
# 记录调用日志
logging.getLogger().setLevel(logging.INFO)
logging.info('call method: %s %s %s', func.__name__, args, kwargs)
return func(*args, **kwargs)

return wrapper


@Logger('airflow')
def task(name):
print(f"run {name}")


if __name__ == '__main__':
task(name="task")

路由映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from functools import wraps
import logging
import flask

class Router(object):
def __init__(self):
self.url_map = {}

def register(self, url):
def wrapper(func):
self.url_map[url] = func

return wrapper

def call(self, url):
func = self.url_map.get(url)
if not func:
raise ValueError('No url function: %s', url)
return func()


router = Router()


@router.register('/page1')
def page1():
return "page1"


print(router.call('page1'))
-------------THANKS FOR READING-------------