概念
python有兩種裝飾器:
- 函數(shù)裝飾器(function decorators)
- 類裝飾器(class decorators)
基礎(chǔ)知識
- 首先函數(shù)名是對函數(shù)的引用,我們可以給同一個函數(shù)多重命名:
>>> def succ(x):
... return x + 1
...
>>> successor = succ
>>> successor(10)
11
>>> succ(10)
11
>>> del succ
>>> successor(10)
11
>>>
- 函數(shù)內(nèi)部可以定義函數(shù):
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!
'''
- 函數(shù)可以作為參數(shù)傳給另外一個函數(shù):
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 09:49:39
#Name:functionAsParameters.py
#Version:V1.0
# 定義一個函數(shù)g
def g():
print("Hi, it's me 'g'")
print("Thanks for calling me")
# 定義一個函數(shù)f,參數(shù)為函數(shù)
def f(func):
print("Hi, it's me 'f'")
print("I will call 'func' now")
func()
# 將函數(shù)g作為參數(shù)傳遞給函數(shù)f
f(g)
你可能不太滿意上述程序的輸出, 函數(shù)f應(yīng)該調(diào)用'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))
- 函數(shù)可以返回函數(shù)
#!/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函數(shù),一個是裝飾前的一個是裝飾后的, 因此python里一般不這樣定義裝飾器.
為了實現(xiàn)裝飾器,python中使用在'@'后接包裝函數(shù)名的方式來定義裝飾器.
#!/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)
總結(jié): 我們可以說徘公,Python中的裝飾器是一個可調(diào)用的Python對象贼涩,用于修改函數(shù)务嫡、方法或類定義匙睹。將要修改的原始對象作為一個參數(shù)傳遞給裝飾者奠宜。裝飾器返回一個修改過的對象绎巨,例如一個修改后的函數(shù)裤纹,該函數(shù)綁定到裝飾器定義中使用的func名稱击碗。
在我們前面定義的裝飾器只能為只有一個參數(shù)的函數(shù)服務(wù),我們下面的例子將會展示更為廣泛的適用:
#!/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裝飾器的應(yīng)用場景
使用裝飾器檢查傳參
下面的程序使用一個裝飾功能逢捺,確保傳遞給函數(shù)的參數(shù)因子是一個正整數(shù):
#!/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))
函數(shù)調(diào)用計數(shù)
下面的例子使用裝飾器對函數(shù)調(diào)用的次數(shù)進行計數(shù):
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)
多參數(shù)實例:
#!/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)
帶參數(shù)的裝飾器
我們先來看看下面的例子:
#!/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")
上例中兩個裝飾器基本相同,我們也可以通過給裝飾器傳遞參數(shù)使得兩個裝飾器合二為一:
#!/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__)
輸出的結(jié)果如下:
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
這個并不是我們想要得到的結(jié)果,如果要得到想要的結(jié)果,我們必須對裝飾函數(shù)做一些更改,保存為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
幸運的是我們并不需要做這些工作,簡單的實現(xiàn)方式是從模塊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__
方法;我們已經(jīng)提到過裝飾器就是一個把函數(shù)當做傳參的簡單可調(diào)用對象.函數(shù)就是一個可調(diào)用對象,我們也可以把類定義胃可調(diào)用對象,__call__
方法可以使類的實例像函數(shù)一樣被調(diào)用:
#!/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)
調(diào)用__call__
方法使用類來實現(xiàn)斐波那契數(shù)列:
#!/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()