概念
python有两种装饰器:
- 函数装饰器(function decorators)
- 类装饰器(class decorators)
基础知识
- 首先函数名是对函数的引用,我们可以给同一个函数多重命名:
>>> def succ(x):
... return x + 1
...
>>> successor = succ
>>> successor(10)
11
>>> succ(10)
11
>>> del succ
>>> successor(10)
11
>>>
- 函数内部可以定义函数:
def f():
def g():
print("Hi, it's me 'g'")
print("Thanks for calling me")
print("This is the function 'f'")
print("I am calling 'g' now:")
g()
f()
'''
This is the function 'f'
I am calling 'g' now:
Hi, it's me 'g'
Thanks for calling me
'''
def temperature(t):
def celsius2fahrenheit(x):
return 9 * x / 5 + 32
result = "It's " + str(celsius2fahrenheit(t)) + " degrees!"
return result
print(temperature(20))
'''
It's 68.0 degrees!
'''
- 函数可以作为参数传给另外一个函数:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 09:49:39
#Name:functionAsParameters.py
#Version:V1.0
# 定义一个函数g
def g():
print("Hi, it's me 'g'")
print("Thanks for calling me")
# 定义一个函数f,参数为函数
def f(func):
print("Hi, it's me 'f'")
print("I will call 'func' now")
func()
# 将函数g作为参数传递给函数f
f(g)
你可能不太满意上述程序的输出, 函数f应该调用'g'而不是'func',为了达到这一点,我们使用属性__name__
:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 09:57:08
#Name:functionAsParameters001.py
#Version:V1.0
def g():
print("Hi, it's me 'g'")
print("Thanks for calling me")
def f(func):
print("Hi, it's me 'f'")
print("I will call 'func' now")
func()
print("func's name is " + func.__name__)
f(g)
另外一个例子
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 10:01:11
#Name:functionAsParameters002.py
#Version:V1.0
import math
def foo(func):
print("The function " + func.__name__ + " was pass to foo.")
res = 0
for x in [1, 2, 2.5]:
res += func(x)
return res
print(foo(math.sin))
print(foo(math.cos))
- 函数可以返回函数
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 10:14:17
#Name:functionReturnFunction.py
#Version:V1.0
def f(x):
def g(y):
return y+x+3
return g
nf1 = f(1)
nf2 = f(3)
print(nf1(1))
print(nf2(1))
定义一个二次方程:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 10:19:41
#Name:functionReturnFunction001.py
#Version:V1.0
def polynomial_creator(a, b, c):
def polynomial(x):
return a * x ** 2 + b * x + c
return polynomial
p1 = polynomial_creator(2, 3, -1)
p2 = polynomial_creator(-1, 2, 1)
for x in range(-2, 2 ,1):
print(x, p1(x), p2(x))
更为复杂的多元方程:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 10:25:38
#Name:functionReturnFunction002.py
#Version:V1.0
def polynomial_creator(*coefficients):
'''
cofficients are in the form a_0, a_1, ... a_n
'''
def polynomial(x):
res = 0
for index, coeff in enumerate(coefficients):
res += coeff * x ** index
return res
return polynomial
p1 = polynomial_creator(4)
p2 = polynomial_creator(2, 4)
p3 = polynomial_creator(2,3,-1,8,1)
p4 = polynomial_creator(-1,2,1)
for x in range(-2, 2, 1):
print(x, p1(x), p2(x) ,p3(x), p4(x))
一个简单的装饰器
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 10:37:12
#Name:aSimpleDecorator.py
#Version:V1.0
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
func(x)
print("After calling " + func.__name__)
return function_wrapper
def foo(x):
print("Hi, foo has been called with " + str(x))
print("We call foo before decoration:")
foo("Hi")
print("We now decorate foo with f:")
foo = our_decorator(foo)
print("We call foo after decoration:")
foo(42)
python装饰器的标准语法
python装饰器的标准语法和我们上例介绍的不太一样,尽管foo = our_decorator(foo)
更容易记忆和理解.在上例子中在同一个程序里我们定义了两个版本的foo函数,一个是装饰前的一个是装饰后的, 因此python里一般不这样定义装饰器.
为了实现装饰器,python中使用在'@'后接包装函数名的方式来定义装饰器.
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 10:56:10
#Name:syntaxForDecorators.py
#Version:V1.0
def our_decorator(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
func(x)
print("After calling " + func.__name__)
return function_wrapper
@our_decorator
def foo(x):
print("Hi, foo has been called with " + str(x))
foo("Hi")
# We can decorate every other function which takes one parameter with our decorator 'our_decorator'.
@our_decorator
def succ(n):
return n + 1
succ(10)
# It is also possible to decorate third party functions
from math import sin,cos
def our_decorator1(func):
def function_wrapper(x):
print("Before calling " + func.__name__)
res = func(x)
print(res)
print("After calling " + func.__name__)
return function_wrapper
sin = our_decorator1(sin)
cos = our_decorator1(cos)
for f in [sin, cos]:
f(3.1415)
总结: 我们可以说,Python中的装饰器是一个可调用的Python对象,用于修改函数、方法或类定义。将要修改的原始对象作为一个参数传递给装饰者。装饰器返回一个修改过的对象,例如一个修改后的函数,该函数绑定到装饰器定义中使用的func名称。
在我们前面定义的装饰器只能为只有一个参数的函数服务,我们下面的例子将会展示更为广泛的适用:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 11:27:11
#Name:syntaxForDecorators001.py
#Version:V1.0
from random import random, randint, choice
def our_decorator(func):
def function_wrapper(*args, **kwargs):
print("Before calling "+func.__name__)
res = func(*args, *kwargs)
print(res)
print("After calling " +func.__name__)
return function_wrapper
random = our_decorator(random)
randint = our_decorator(randint)
choice = our_decorator(choice)
random()
randint(3,8)
choice([4, 5, 6])
python装饰器的应用场景
使用装饰器检查传参
下面的程序使用一个装饰功能,确保传递给函数的参数因子是一个正整数:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 11:44:58
#Name:checkingArguments.py
#Version:V1.0
def argument_test_natural_number(f):
def helper(x):
if type(x) == int and x >0:
return f(x)
else:
raise Exception("Argument is not an integer")
return helper
@argument_test_natural_number
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
for i in range(1, 10):
print(i, factorial(i))
print(factorial(-1))
函数调用计数
下面的例子使用装饰器对函数调用的次数进行计数:
def call_counter(func):
def helper(x):
helper.calls += 1
return func(x)
helper.calls = 0
return helper
@call_counter
def succ(x):
return x + 1
print(succ.calls)
for i in range(10):
succ(i)
print(succ.calls)
多参数实例:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 11:58:57
#Name:countingFunctionCalls001.py
#Version:V1.0
def call_counter(func):
def helper(*args, **kwargs):
helper.calls += 1
return func(*args, **kwargs)
helper.calls = 0
return helper
@call_counter
def succ(x):
return x + 1
@call_counter
def mull(x, y=1):
return x*y + 1
print(succ.calls)
for i in range(10):
succ(i)
mull(3,4)
mull(4)
mull(y=3, x=2)
print(succ.calls)
print(mull.calls)
带参数的装饰器
我们先来看看下面的例子:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 14:44:43
#Name:decoratorsWithParameters1.py
#Version:V1.0
def evening_greeting(func):
def function_wrapper(x):
print("Good evening, " + func.__name__ +" returns:")
func(x)
return function_wrapper
def moning_greeting(func):
def function_wrapper(x):
print("Good morning, " + func.__name__ + " returns:")
func(x)
return function_wrapper
@evening_greeting
def foo(x):
print(42)
foo("Hi")
上例中两个装饰器基本相同,我们也可以通过给装饰器传递参数使得两个装饰器合二为一:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 14:58:38
#Name:decoratorsWithParameters2.py
#Version:V1.0
def greeting(expr):
def greeting_decorator(func):
def function_wrapper(x):
print(expr + ", " + func.__name__ + " returns:")
func(x)
return function_wrapper
return greeting_decorator
@greeting("Good morning, ")
def foo(x):
print(42)
foo("Hi")
这种方式相当于:
greeting2 = greeting("Good morning, ")
foo = greeting2(foo)
或者:
foo = greeting("Good morning, ")(foo)
导入装饰器
如果装饰器是从其他模块导入的话, 将会失去以下属性:
-
__name__
name of the function -
__doc__
the docstring -
__module__
the module in which the function is defined
首先我们在'greeting_decorator.py'中定义一个装饰器
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 15:15:48
#Name:greeting_decorator.py
#Version:V1.0
def greeting(func):
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return function_wrapper
然后我们从另一个程序中导入这个装饰器:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 15:19:49
#Name:call_greeting_decorator.py
#Version:V1.0
from greeting_decorator import greeting
@greeting
def f(x):
""" just some silly function """
return x + 4
f(10)
print("function name: " + f.__name__)
print("docstring: " + f.__doc__)
print("module name: " + f.__module__)
输出的结果如下:
fbo@fbo-virtual-machine:~/tmp/py$ python3 call_greeting_decorator.py
Hi, f returns:
function name: function_wrapper
docstring: function_wrapper of greeting
module name: greeting_decorator
这个并不是我们想要得到的结果,如果要得到想要的结果,我们必须对装饰函数做一些更改,保存为greeting_decorator_manually.py
:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 15:27:53
#Name:greeting_decorator_manually.py
#Version:V1.0
def greeting(func):
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
function_wrapper.__name__ = func.__name__
function_wrapper.__doc__ = func.__doc__
function_wrapper.__module__ = func.__module__
return function_wrapper
幸运的是我们并不需要做这些工作,简单的实现方式是从模块functools
导入装饰器wraps
,例如:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 15:15:48
#Name:greeting_decorator.py
#Version:V1.0
from functools import wraps
def greeting(func):
@wraps(func)
def function_wrapper(x):
""" function_wrapper of greeting """
print("Hi, " + func.__name__ + " returns:")
return func(x)
return function_wrapper
类装饰器
__call__
方法
在介绍类装饰器之前我们先来介绍以下类的__call__
方法;我们已经提到过装饰器就是一个把函数当做传参的简单可调用对象.函数就是一个可调用对象,我们也可以把类定义胃可调用对象,__call__
方法可以使类的实例像函数一样被调用:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 16:05:51
#Name:callClass.py
#Version:V1.0
class A:
def __init__(self):
print("An instance of A was initialized")
def __call__(self, *args, **kwargs):
print("Arguments are:", args, kwargs)
x = A()
print("now calling the instance:")
x(3, 4, x=11, y=10)
print("Let's call it again:")
x(3, 4, x=11, y=10)
调用__call__
方法使用类来实现斐波那契数列:
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 16:10:22
#Name:callClass1.py
#Version:V1.0
class Fibonacci:
def __init__(self):
self.cache = {}
def __call__(self, n):
if n not in self.cache:
if n == 0:
self.cache[0] = 0
elif n == 1:
self.cache[1] = 1
else:
self.cache[n] = self.__call__(n - 1) + self.__call__(n-2)
return self.cache[n]
fib = Fibonacci()
for i in range(15):
print(fib(i), end = ", ")
print()
使用类作为装饰器
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 16:25:20
#Name:classDecorator.py
#Version:V1.0
class decorator:
def __init__(self,f):
self.f = f
def __call__(self):
print("Decorating", self.f.__name__)
self.f()
@decorator
def foo():
print("inside foo()")
foo()