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