Python进阶二

关于python进阶的方向和知识点很多,今天聊聊装饰器。

过年之前网上逛到一本Python进阶文档,除了一些零散的知识点,里面关于装饰器的讲解挺不错的。

魔法变量args和kwargs

首先 *args**kwargs只有前面的星号才是必须的,args和kwargs是一个通俗的命名约定。

这两个参数适合与参数数量不定的函数调用处理。args用于普通参数,kwargs用于键值对。

1
2
3
4
5
6
7
8
9
def test_var_args(f_args, *args, **kwargs):
print(f_args)

for arg in args:
print(arg)
for key, value in kwargs.items():
print("{0} :{1}".format(key, value))

test_var_args("hello", "args_0", "hargs_1", key0="value0", key1="value1")

这个特性可以装饰器对函数进行重新封装的时候用到。

函数对象

Python的函数其实本质上就是对象,可以对函数进行二次封装。

1
2
3
4
5
def add(a,b):
return a+b

add_new=add
print(add_new(1,2))

引入一个概念,高阶函数,可以接受参数为函数的函数。

高阶函数与装饰器

因此,我们可以在定义一个高阶函数,对接受进来的函数进行修改,重新返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def with_log(func):
def new_func(a, b):
print("function {} start.".format(new_func.__name__))
print("start function")
func(a, b)
print("function {} end.".format(new_func.__name__))

return new_func

@with_log
def add(a, b):
return a + b

add(1, 2)

上面的with_log就是一个装饰器,对已经定义好的函数进行修饰。在需要装饰的函数定义上加一朵花,然后就可以对这个函数增加一些装饰。

完善的装饰器

上面的装饰器有个问题,我们调用的是add,但是打印出来的函数名字是new_func,这显然不是我们想要的。正确的做法如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from functools import wraps
def with_log(func):

@wraps(func)
def new_func(a, b):
print("function {} start.".format(new_func.__name__))
print("start function")
func(a, b)
print("function {} end.".format(new_func.__name__))

return new_func

@with_log
def add(a, b):
return a + b

add(1, 2)

另外还有一个问题。如果要将这个装饰器用到其他函数上,那么里面的参数个数,就不能够写死为add函数的参数。此时,前面讲到魔法变量便排上了用场。

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

def with_log(func):

@wraps(func)
def new_func(a, b):
print("function {} start.".format(new_func.__name__))
print("start function")
func(*args, **kwargs)
print("function {} end.".format(new_func.__name__))

return new_func

@with_log
def add(a, b):
return a + b

@with_log
def print_hello():
print("hello")

add(1, 2)
print_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
from functools import wraps
import datetime
timestamp = datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S')

def logit(logfile="run_{}.log".format(timestamp)):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + ' was called'
with open(logfile, "a") as opened_file:
opened_file.write(log_string+'\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator

@logit()
def test():
pass

@logit(logfile="run.log")
def test1():
pass

test()
test1()

装饰器是对函数进行二次封装,是一种基础的函数式编程的实现。

总结

魔法变量和函数对象特性为装饰器的实现提供了可能,在具体的使用的时候需要借助functools的wrap来保证内置变量的正确性。二层封装实现了带参数的装饰器。

装饰器是基本的函数式编程,很Pythonic。后续的进阶要针对面向对象概念或者框架进行。