python修饰器教程

本文部分翻译自Python Decorator Tutorial with Example,如有疑问,请联系我更改,谢谢。


这篇文章是对于python中修饰器(decorator)的简单介绍和应用举例,如果需要深究,不妨移步exhaustive articles about decorator。在python中,所有的东西都是对象,即便是函数也不例外。这意味着函数也是可以被赋值给一个变量的,也是可以作为参数传递给另外一个函数的,也是可以作为另一个函数的返回值的。这些性质是如此的美妙,导致有着一大堆奇妙的用法。下面给一个例子:

def outer_function():
    print("1. This is outer function!")
    def inner_function():
        print("2. This is inner function, inside outer function!")
    print("3. This is outside inner function, inside outer function!")
    return inner_function()

# call the function
outer_function()

# output
1. This is outer function!
3. This is outside inner function, inside outer function!
2. This is inner function, inside outer function!

在上面的运行过程中,我们可以发现,内部函数inner_function的输出是最后的(也就是第二号),这个过程紧接着发生在inner_function被返回后,在outer_function的结尾。
python的修饰器是可以接受函数在参数,并且返回另一个函数作为返回值的一种函数。作为一个修饰器,我们最基本的用法就是将函数作为一个参数传入,并且修饰器内的内部函数inner_function的签名必须要实现最主要的功能,也就是修饰功能(这里描述得不清楚,我们看后面的例子)。

作为函数的修饰器

修饰器可以作为函数的修饰器,我们现在不妨写一个简单的函数修饰器作为例子,我们将会编写一个修饰器,它可以测量一个函数的运行时间。让我们不妨先想一下,如果不用修饰器我们会怎么实现这个功能呢?一般来说应该是:

import time 
begin = time.clock()
ret = func(*arg, **kwargs)
end = time.clock()
print(end-begin)

然而这种方法很不灵活,每次都需要自己手动添加开始时间和结束时间,非常麻烦,如果利用修饰器,则可以优雅地完成这个任务,如:

import time

def timetest(input_func):
    def timed(*args, **kwargs):
        begin = time.clock()
        result = input_func(*args, **kwargs)
        end = time.clock()
        print(end-begin)
        return result
    return timed
@timetest
def foobar(*args, **kwargs):
    time.sleep(0.3)
    print('test world')

foobar(["hello, world"], foo=2, bar=5)
# outputs
# test world
# 0.0016730000000000356

我们将函数foobar传入名为timetest的修饰器中,在这个修饰器中,函数foobar是被引用为变量input_func,然后结果就是,input_func的运行结果将会作为返回值进行返回,而在其之前之后就可以添加一些你需要的功能,比如测量时间了。
在修饰器的名字前面前缀上@符号,就可以调用修饰器修饰你的函数了,你的修饰器函数名需要和你编写的修饰器名字一样,然后修饰器下面的函数将会被作为需要被传入的函数,作为参数传入修饰器。这个过程称之为修饰。

方法修饰器

方法是类内部的“函数”,和函数不太一样的是,他和类密切相关。方法修饰器允许你通过修饰器重写类属性,而不需要找到被调用的那个函数,显式地进行修改。

def method_decorator(method):
    def inner(city_instance):
        if city_instance.name == "SFO":
            print("Its a cool place to live in.")
        else:
            method(city_instance)
    return inner


class City(object):

    def __init__(self, name):
        self.name = name

    @method_decorator
    def print_test(self):
        print(self.name)

p1 = City("SFO")
p1.print_test()

# output
# Its a cool place to live in.

在上面的代码片段中,我们修饰了类方法print_test,其中,如果城市实体的名字不是”SFO”的话,method_decorator将打印出城市的名字。试想,如果不用修饰器的话,如果在原先的打印城市名字的方法中添加判断是否是“SFO”的功能,那么你必须找到这个print_test方法,然后显式地添加这个功能,如果以后的开发迭代过程中你还需要更改,那么你又需要再次找到这个方法,并且再次更改,这个是一个很麻烦的事情,因此好好利用修饰器吧,他能让你的代码变得优雅。

基于类的修饰器

如果你想要创建一个可调用的object,其可以返回另一个可调用的object,那么函数修饰器是一个很棒的方法。如果你想要返回值是一个函数,你也应该优先考虑函数修饰器。然而,如果你想要修饰器返回一个可定制的object,其可以完成一些不同于函数所能完成的事情,这个情况下,一个类修饰器也许应该优先被考虑。(换句话说,也就是这个修饰器函数可能比较复杂,不能或者很难用一个简单的函数表示,这个时候你可以用类这个数据形式表示修饰器。)
在一个类中,你可以添加方法和属性,用这些去修饰可调用对象,或者完成在其上面(指的是可调用对象)的操作。

class decoclass(object):

    def __init__(self, f):
        self.f = f

    def __call__(self, *args, **kwargs):
        # before f actions
        print('decorator initialised')
        self.f(*args, **kwargs)
        print('decorator terminated')
        # after f actions

@decoclass
def klass():
    print('class')

klass()
# output
# decorator initialised
# class
# decorator terminated

这样,我们可以让修饰器变得更加的复杂和灵活,可以用更复杂的修饰器完成更加美妙的功能。

链式修饰器

修饰器的链式调用类似于多继承,其可以用于构造类。其实,为了修饰一个函数,你想要用多少修饰器都是没问题的,你只需要把他们一个接一个“链接”起来就行了。我们看一下例子:

def makebold(f):
    return lambda: "<b>" + f() + "</b>"
def makeitalic(f):
    return lambda: "<i>" + f() + "</i>"

@makebold
@makeitalic
def say():
    return "Hello"

print(say())
# output
# <b><i>Hello</i></b>

你需要牢记在心的是,修饰器的链接顺序是很关键的,也就是说链式修饰器是对顺序敏感的,当你层叠修饰器的时候,他们开始层叠的顺序是从下至上,也就是最下层的修饰器是最优先运行的,最上层的最后。

functools工具和包裹(wrap)

当我们使用修饰器的时候,我们其实是在用一个函数去替代另一个函数。

def decorator(func):
    """decorator docstring"""
    def inner_function(*args, **kwargs):
        """inner function docstring """
        print(func.__name__ + "was called")
        return func(*args, **kwargs)
    return inner_function


@decorator
def foobar(x):
    """foobar docstring"""
    return x**2

如果我们尝试打印出函数foobar的名字和docstring的话,我们会发现:

print(foobar.__name__)
print(foobar.__doc__)

# output
# inner_function
# inner function docstring 

我们发现,函数foobar其实是被函数inner_function替代掉了,这个意味着我们丢失了关于我们传入修饰器(也就是被修饰函数)的信息,这在一些应用中是不利于整体系统架设的。而functools.wraps就是为了解决这个问题而被创造出来的。它可以接管被修饰函数,并且在修饰器中添加被修饰函数的名字,docstring和其他信息的情况下传递被修饰体到修饰器中。让我们看一下使用修饰器而不损失被修饰函数信息的例子:

from functools import wraps

def wrapped_decorator(func):
    """wrapped decorator docstring"""
    @wraps(func)
    def inner_function(*args, **kwargs):
        """inner function docstring """
        print(func.__name__ + "was called")
        return func(*args, **kwargs)
    return inner_function


@wrapped_decorator
def foobar(x):
    """foobar docstring"""
    return x**2

print(foobar.__name__)
print(foobar.__doc__)

# output
# foobar
# foobar docstring

以上的实现保留了被修饰函数的信息,可以更为灵活的被使用。

至于怎么在一个基于类的修饰器中保留被修饰函数信息,可以参考One of the ways of doing it, is listed here

带参数的修饰器

带参数的函数修饰器

from functools import wraps
def decorator(arg1, arg2):
    def inner_function(function):
        @wraps(function)
        def wrapper(*args, **kwargs):
            print("Arguements passed to decorator %s and %s" % (arg1, arg2))
            function(*args, **kwargs)
        return wrapper
    return inner_function

@decorator("arg1", "arg2")
def print_args(*args):
    for each in args:
        print(each)

print_args(1, 2, 3)
# output
# Arguements passed to decorator arg1 and arg2
# 1
# 2
# 3

带参数的基于类的修饰器

class ClassDecorator(object):

    def __init__(self, arg1, arg2):
        print("Arguements passed to decorator %s and %s" % (arg1, arg2))
        self.arg1 = arg1
        self.arg2 = arg2

    def __call__(self, foo, *args, **kwargs):

        def inner_func(*args, **kwargs):
            print("Args passed inside decorated function .%s and %s" % (self.arg1, self.arg2))
            return foo(*args, **kwargs)
        return inner_func

@ClassDecorator("arg1", "arg2")
def print_args(*args):
    for arg in args:
        print(arg)

print_args(1, 2, 3)
# output
# Arguements passed to decorator arg1 and arg2
# Args passed inside decorated function .arg1 and arg2
# 1
# 2
# 3

如果你的修饰器是带有可选参数的,请参考这篇文章Making decorators with optional arguments

posted @ 2018-08-25 22:22  FesianXu  阅读(95)  评论(0编辑  收藏  举报