首页 > 代码库 > Python 函数装饰器入门
Python 函数装饰器入门
Python 函数装饰器入门
原文链接: --> A guide to Python‘s function decorators
Python功能强劲,语法表现力强,尤其装饰器深深的吸引着我。在设计模式中,装饰器可以在不使用子类的情况下,动态的改变函数,方法以及类的功能。这个功能非常有用,特别在你想扩展函数的功能同时又不想改变原有的函数。的确,我们任意的实现装饰器设计模式,但是,python通过提供简单的语法和特性让装饰器的实现变的如此简单。
在本文中,我将用一组例子来深入浅入python 函数装饰器的功能,所有的例子都是在python2.7中测试通过的,如果应用到python3中,需要做一些修改。
本质上,装饰器就是包装的作用,改变函数执行前后的行为,但是不影响函数的执行。
你需要知道的函数知识
在讲之前,有一些关于python函数的基本特性需要说明一下
函数可以赋值给一个变量
def hello(name): return ‘Hello ‘ + namehello_someone = hellohello_some(‘world‘)# Outputs: hello world
在函数内部可以定义函数
def hello(name): def get_message(): return ‘Hello ‘ result = get_message() + name return resultprint hello(‘world‘)# Outputs: Hello world
函数可以作为另外一个函数的参数
def hello(name): return ‘Hello ‘ + namedef call_func(func): welcome_name = ‘world‘ return func(welcome_name)print call_func(hello)# Outputs: Hello world
函数可以作为函数的返回值
也就是说函数可以生成其他的函数
def compose_hello_func(): def get_message(): return ‘Hello world‘ return get_messagehello = compose_hello_func()print hello()# Outputs: Hello world
内部函数可以访问闭包区域
这就是闭包,在我们建装饰器时非常有用的一个模式
def compose_hello_func(name): def get_message(): return ‘Hello ‘ + name return get_messagehello = compose_hello_func(‘world‘)print hello()# Outputs: Hello world
实现一个装饰器
函数装饰器就是去粉饰现存的函数的,把上面的想法糅合一下,我们就可以构造一个装饰器。在下面的例子中,我们用p标签去装饰另外一个函数的字符串输出
def get_text(name): return "hello {0}, good morning".format(name)def p_decorator(func): def func_wrapper(name): return "<p>{0}</p>".format(func(name)) return func_wrappermy_get_text = p_decorator(get_text)print my_get_text(‘world‘)# Output: <p>hello world, good morning</p>
这就是我们的第一个装饰器,将一个函数作为输入,生成另外一个函数,同时充实了原有函数的功能,返回了一个可以随意使用的函数,为了能够让get_text
函数能够被装饰器p_decorator
装饰,只需要将p_decorator
的结果赋给get_text
即可。
get_text = p_decorator(get_text)print get_text(‘world‘)# Output: <p>hello world, good morning</p>
Python的装饰器语法
python通过语法糖(syntactic sugar)可以使用和创建一个简洁清爽的装饰器。像上文中的装饰get_text
函数,我们不需要使用get_text = p_decorator(get_text)
,python给我们实现了一种简单的方式,只要通过@
符号即可实现。
def p_decorator(func): def func_wrapper(name): return "<p>{0}</p>".format(func(name)) return func_wrapper@p_decoratordef get_text(name): return "hello {0}, good morning".format(name)print get_text(‘world‘)# Output: <p>hello world, good morning</p>
现在我们想对get_text
函数再装饰两次,分别使用div
和strong
tag去装饰get_text
的字符串输出
def p_decorator(func): def func_wrapper(name): return "<p>{0}</p>".format(func(name)) return func_wrapperdef strong_decorator(func): def func_wrapper(name): return "<strong>{0}</strong>".format(func(name)) return func_wrapper def div_decorator(func): def func_wrapper(name): return "<div>{0}</div>".format(func(name)) return func_wrapper
有了这些基本的方法,装饰get_text
只需要如下的表达式:
get_text = div_decorate(p_decorate(strong_decorate(get_text)))
利用python的语法糖,同样的事情,实现起来却更具有表现力
@div_decorator@strong_decorator @p_decoratordef get_text(name): return "hello {0}, good morning".format(name)print get_text(‘world‘)# Outputs: <div><strong><p>hello world, good morning</p></strong></div>
一个需要注意的事情就是,装饰器的顺序不同,最后得到结果也会不一样。
装饰方法
在python中,装饰器也可以修饰class里面的函数
def p_decorator(func): def wrapper(self): return "<p>{0}</p>".format(func(self)) return wrapperclass Person(object): def __init__(self): self.name = ‘hello‘ self.family = ‘world‘ @p_decorator def get_fullname(self): return self.name + " " + self.familymy_person = Person()print my_person.get_fullname()#Output: <p>hello world</p>
一种更好的方法可以让我们的装饰器变得更为通用,就是将\*args
和\*\*kwargs
作为参数传给装饰函数,这样装饰函数就可以接受任意个单参数和key = value
类型的参数
def p_decorator(func): def wrapper(*args, **kwargs): return "<p>{0}</p>".format(func(*args, **kwargs)) return wrapperclass Person(object): def __init__(self): self.name = ‘hello‘ self.family = ‘world‘ @p_decorator def get_fullname(self): return self.name + " " + self.familymy_person = Person()print my_person.get_fullname()#Output: <p>hello world</p>
传递参数给装饰器
在上面的几个tag
装饰器,看起来有很大的冗余,那是不是可以传参数给装饰器呢,当然是可以的。
def tags(tag_name): def tags_decorator(func): def func_wrapper(name): return "<{0}>{1}</{0}>".format(tag_name, func(name)) return func_wrapper return tags_decorator @tags("any_tag")def get_text(name): return "Hello " + nameprint get_text("World")#Output: <any_tag>Hello World</any_tag>
实际上函数tags
和函数tags_decorator
都是用来修饰函数get_text
的。
函数装饰器的小bug
我们先看一段代码
def p_decorator(func): def wrapper(name): ‘‘‘wrapper docs‘‘‘ return "<p>{0}</p>".format(func(name)) return wrapper @p_decoratordef get_text(name): ‘‘‘hello docs‘‘‘ return "Hello " + name print get_text.__doc__ print get_text.__name__# Ouput: #wrapper docs#wrapper
我们发现get_text
被装饰器修饰过后,它的函数名和doc信息都变成了装饰函数的了。 有方法解决吗,当然,python为我们提供了functool模块中的wraps函数,该函数会会将装饰函数的特性(attribute)更新为原来的函数。
from functools import wraps def p_decorator(func): ‘‘‘wrap docs‘‘‘ @wraps(func) def wraper(name): return "<p>{0}</p>".format(func(name)) return wraper@p_decoratordef get_text(name): ‘‘‘hello docs‘‘‘ return "Hello " + name print get_text.__doc__ print get_text.__name__ # Output: #hello docs#get_text
总结
本文的例子相比于真正使用的装饰来说是比较简单的。记住一点,装饰器实际上就是在不改变函数本身的情况下,扩展函数的功能,详细的信息可以参考Python Decorator Library
<style></style>
Python 函数装饰器入门