Jusene's Blog

理解python装饰器

字数统计: 1.6k阅读时长: 7 min
2017/10/31 Share

装饰器

人要衣装,佛要金装,函数要装饰器。要了解装饰器,首先我们需要了解高阶函数,函数在python中作为一级类对象,可以被当作参数传递,也作为函数返回,简单来说高阶函数就是不是单一函数对象,里面函数嵌套函数,所以这么说python是支持函数式编程的,而装饰器首先它肯定是一个高阶函数,本质即是接受函数作为参数,并且返回一个函数,装饰器装饰了函数,可以在函数执行的前后做一些操作,外层函数的作用域的变量(但不是全局变量)可以被内部函数应用,这又是python闭包的概念,装饰器集合了许多python函数的概念,所以一直被称为最难掌握的内容。

理解装饰器

先简单的来一个例子:

1
import time
2
def timeit(some_func):
3
	def inner(*args,**kwargs):
4
		start=time.time()
5
		ret=some_func(*args,**kwargs)
6
		spend_time=time.time()-start
7
		return spend_time
8
	return inner
9
10
def sleep(x):
11
	time.sleep(x)
12
13
decorated=timeit(sleep)
14
decorated(3)
15
3.00376296043396

结合闭包和函数的概念,整个函数应该很容易理解,执行的过程如:timeit(sleep) -> inner(3),我们将sleep函数作为参数传入timeit,timeit返回inner函数,根据闭包(函数+应用环境),在外层函数的应用环境被引入inner内层函数,所以很顺利的sleep函数与inner函数就建立了连接,在inner函数中的some_func即是sleep函数。所以说到这,装饰器也可以说是一个闭包。

更深一步,我们加入语法糖了尝试下:

1
import time
2
def timeit(some_func):
3
	def inner(*args,**kwargs):
4
		start=time.time()
5
		ret=some_func(*args,**kwargs)
6
		spend_time=time.time()-start
7
		return spend_time
8
	return inner
9
10
@timeit
11
def sleep(x):
12
	time.sleep(x)
13
14
sleep(3)
15
3.001178741455078

整个引用过程变得更加简单,而根据上一个函数我们可以得出,不带参数的装饰器可以理解做的是:sleep=timeit(sleep)

上面我们说是不带参数的装饰器,那么相反的有带参数的装饰器:

1
import time
2
def timeit(offset=0):
3
	def outer(some_func):
4
		def inner(*args,**kwargs):
5
			start=time.time()
6
			ret=some_func(*args,**kwargs)
7
			spend_time=time.time()-start+offset
8
			return spend_time
9
		return inner
10
	return outer
11
12
def sleep(x):
13
	time.sleep(x)
14
15
dec=timeit(1)
16
dec2=dec(sleep)
17
dec2(3)
18
4.002138137817383

相信理解了不带参数的参数的装饰器,对于这个拆解的带参数的装饰器也是比较好理解的,执行过程:timeit(1) -> outer(sleep) -> inner(3),其实说到这,我是怎么理解装饰器就是多层次的闭包。

我们再用python语法糖来尝试下带参数的装饰器:

1
import time
2
def timeit(offset=0):
3
	def outer(some_func):
4
		def inner(*args,**kwargs):
5
			start=time.time()
6
			ret=some_func(*args,**kwargs)
7
			spend_time=time.time()-start+offset
8
			return spend_time
9
		return inner
10
	return outer
11
12
@timeit(1)
13
def sleep(x):
14
	time.sleep(x)
15
16
sleep(3)
17
4.004631042480469

根据上边的语法糖的理解,带参数的装饰器的过程应该是:sleep=timeit(1)(sleep)

这里需要说下的是带参数的装饰器的语法糖,后边只允许带一个参数括号

还有一种用法就是多个装饰器的叠加,这里也试着写个例子说明下:

1
def deco_1(func):
2
	print('enter into deco_1')
3
	def wrapper(a,b):
4
		print('enter into deco_1_wrapper')
5
		func(a,b)
6
	return wrapper
7
8
def deco_2(func):
9
	print('enter into deco_2')
10
	def wrapper(a,b):
11
		print('enter into deco_2_wrapper')
12
		func(a,b)
13
	return wrapper
14
15
@deco_1
16
@deco_2
17
def addFun(a,b):
18
	print('result is %d' % (a+b))
19
20
addFun(1,2)
21
enter into deco_2
22
enter into deco_1
23
enter into deco_1_wrapper
24
enter into deco_2_wrapper
25
result is 3

从这个例子中,可以看出多层套接字是从下向上执行的:addFun(1,2)=deco_1(deco_2(addFun(1,2)))

类装饰器

使用类装饰器,还可以依靠类的内部的call方法,当使用@形式将装饰器附加到函数上时,就会调用此方法。

1
2
class foo(object):
3
	def __init__(self,func):
4
		self._func=func
5
	def __call__(self):
6
		print('class decorater running')
7
		self._func()
8
		print('class decorator stopping')
9
10
@foo
11
def bar():
12
	print('bar')
13
14
bar()
15
class decorater running
16
bar
17
class decorator stopping

保留原函数的信息

1
import time
2
def timeit(some_func):
3
	def inner(*args,**kwargs):
4
		start=time.time()
5
		ret=some_func(*args,**kwargs)
6
		spend_time=time.time()-start
7
		return spend_time
8
	return inner
9
10
@timeit
11
def sleep(x):
12
	''' sleep '''
13
	time.sleep(x)
14
15
print(sleep.__name__,sleep.__doc__)
16
17
inner None

我们知道在整个生成器的执行过程,我们经过了sleep=timeit(sleep),所以在我们获得sleep的时候函数的内存指针应该指向的inner的函数,所以原本sleep的函数信息就全部没了,成了一个新函数,而为了避免这种情况,functools中wraps可以将原本的函数属性全部更新到新函数中。

1
from functools import wraps
2
import time
3
def timeit(some_func):
4
	@wraps(some_func)
5
	def inner(*args,**kwargs):
6
		start=time.time()
7
		ret=some_func(*args,**kwargs)
8
		spend_time=time.time()-start
9
		return spend_time
10
	return inner
11
12
@timeit
13
def sleep(x):
14
	''' sleep '''
15
	time.sleep(x)
16
17
print(sleep.__name__,sleep.__doc__)
18
19
sleep  sleep

简单的装饰器的应用

auth认证

1
#!/usr/bin/env python
2
#-*- coding=utf8 -*-
3
4
def checkpass(func):
5
	def inner():
6
		if pass_database.get(user,None) == password:
7
			ret=func()
8
			return ret
9
		else:
10
			print('Password is not correct.')
11
	return inner
12
13
def checkuser(func):
14
	def inner():
15
		if user in user_database:
16
			ret=func()
17
			return ret
18
		else:
19
			print('User is not present.')
20
	return inner
21
22
@checkuser
23
@checkpass
24
def index():
25
	print('Hello {}!'.format(user))
26
27
user_database=['jusene','jhon']
28
pass_database={'jusene':'1234','jhon':'1234'}
29
30
user=input('请输入user: ')
31
password=input('请输入pass: ')
32
index()
33
34
➜  ~ python test.py
35
请输入user: jusene
36
请输入pass: 1234
37
Hello jusene!
38
➜  ~ python test.py
39
请输入user: j
40
请输入pass: 1234
41
User is not present.
42
➜  ~ python test.py
43
请输入user: j
44
请输入pass: 21
45
User is not present.
46
➜  ~ python test.py
47
请输入user: jusene
48
请输入pass: 222
49
Password is not correct.

cache缓存

1
#!/usr/bin/env python
2
#-*- coding=utf8 -*-
3
4
from functools import wraps
5
6
def cache(instance):
7
    def dec(fn):
8
        @wraps(fn)
9
        def wrap(*args,**kwargs):
10
            pos=','.join([str(x) for x in args])
11
            kw=','.join(['{}={}'.format(k,v) for k,v in sorted(kwargs.items())])
12
            key='{}::{}::{}'.format(fn.__name__,pos,kw)
13
            print(key)
14
            ret=instance.get(key)
15
            if ret is not None:
16
                return ret
17
            ret=fn(*args,**kwargs)
18
            instance.set(key,ret)
19
            return ret
20
        return wrap
21
    return dec
22
23
class DictCache:
24
    def __init__(self):
25
        self.cache=dict()
26
    def get(self,key):
27
        return self.cache.get(key)
28
    def set(self,key,value):
29
        self.cache[key]=value
30
    def __str__(self):
31
        return str(self.cache)
32
    def __repr__(self):
33
        return str(self.cache)
34
35
cache_instance=DictCache()
36
37
import time
38
@cache(cache_instance)
39
def long_time_fun(x):
40
    time.sleep(x)
41
    return x
42
43
>>> long_time_fun(3)
44
long_time_fun::3::    #需要等3秒
45
3
46
>>> long_time_fun(3)
47
long_time_fun::3::    #秒出
48
3
49
50
>>> long_time_fun(x=3)
51
long_time_fun::::x=3
52
3
53
>>> long_time_fun(x=3)
54
long_time_fun::::x=3
55
3
CATALOG
  1. 1. 装饰器
  2. 2. 理解装饰器
  3. 3. 类装饰器
  4. 4. 保留原函数的信息
  5. 5. 简单的装饰器的应用
    1. 5.1. auth认证
    2. 5.2. cache缓存