首页 > 代码库 > 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函数再装饰两次,分别使用divstrongtag去装饰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 函数装饰器入门