解惑,從新認(rèn)識python裝飾器

概念

python有兩種裝飾器:

  • 函數(shù)裝飾器(function decorators)
  • 類裝飾器(class decorators)

基礎(chǔ)知識

  1. 首先函數(shù)名是對函數(shù)的引用,我們可以給同一個(gè)函數(shù)多重命名:
>>> def succ(x):
...     return x + 1
... 
>>> successor = succ
>>> successor(10)
11
>>> succ(10)
11
>>> del succ
>>> successor(10)
11
>>> 
  1. 函數(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!
'''
  1. 函數(shù)可以作為參數(shù)傳給另外一個(gè)函數(shù):
#!/usr/bin/env python3
#Author:fbo
#Time:2017-12-06 09:49:39
#Name:functionAsParameters.py
#Version:V1.0

# 定義一個(gè)函數(shù)g
def g():
    print("Hi, it's me 'g'")
    print("Thanks for calling me")

# 定義一個(gè)函數(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',為了達(dá)到這一點(diǎn),我們使用屬性__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)

另外一個(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))
  1. 函數(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))

定義一個(gè)二次方程:

#!/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))

更為復(fù)雜的多元方程:

#!/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))

一個(gè)簡單的裝飾器

#!/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裝飾器的標(biāo)準(zhǔn)語法

python裝飾器的標(biāo)準(zhǔn)語法和我們上例介紹的不太一樣,盡管foo = our_decorator(foo)更容易記憶和理解.在上例子中在同一個(gè)程序里我們定義了兩個(gè)版本的foo函數(shù),一個(gè)是裝飾前的一個(gè)是裝飾后的, 因此python里一般不這樣定義裝飾器.
為了實(shí)現(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中的裝飾器是一個(gè)可調(diào)用的Python對象,用于修改函數(shù)、方法或類定義。將要修改的原始對象作為一個(gè)參數(shù)傳遞給裝飾者。裝飾器返回一個(gè)修改過的對象,例如一個(gè)修改后的函數(shù),該函數(shù)綁定到裝飾器定義中使用的func名稱。

在我們前面定義的裝飾器只能為只有一個(gè)參數(shù)的函數(shù)服務(wù),我們下面的例子將會(huì)展示更為廣泛的適用:

#!/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)用場景

使用裝飾器檢查傳參

下面的程序使用一個(gè)裝飾功能,確保傳遞給函數(shù)的參數(shù)因子是一個(gè)正整數(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)用計(jì)數(shù)

下面的例子使用裝飾器對函數(shù)調(diào)用的次數(shù)進(jìn)行計(jì)數(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ù)實(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")

上例中兩個(gè)裝飾器基本相同,我們也可以通過給裝飾器傳遞參數(shù)使得兩個(gè)裝飾器合二為一:


#!/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")

這種方式相當(dāng)于:

greeting2 = greeting("Good morning, ")
foo = greeting2(foo)

或者:

foo = greeting("Good morning, ")(foo)

導(dǎo)入裝飾器

如果裝飾器是從其他模塊導(dǎo)入的話, 將會(huì)失去以下屬性:

  • __name__ name of the function
  • __doc__ the docstring
  • __module__ the module in which the function is defined

首先我們在'greeting_decorator.py'中定義一個(gè)裝飾器

#!/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

然后我們從另一個(gè)程序中導(dǎo)入這個(gè)裝飾器:


#!/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

這個(gè)并不是我們想要得到的結(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

幸運(yùn)的是我們并不需要做這些工作,簡單的實(shí)現(xiàn)方式是從模塊functools導(dǎo)入裝飾器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)提到過裝飾器就是一個(gè)把函數(shù)當(dāng)做傳參的簡單可調(diào)用對象.函數(shù)就是一個(gè)可調(diào)用對象,我們也可以把類定義胃可調(diào)用對象,__call__方法可以使類的實(shí)例像函數(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__方法使用類來實(shí)現(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()
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 〇、前言 本文共108張圖,流量黨請慎重! 歷時(shí)1個(gè)半月,我把自己學(xué)習(xí)Python基礎(chǔ)知識的框架詳細(xì)梳理了一遍。 ...
    Raxxie閱讀 19,602評論 17 410
  • 本文為《爬著學(xué)Python》系列第四篇文章。從本篇開始,本專欄在順序更新的基礎(chǔ)上,會(huì)有不規(guī)則的更新。 在Pytho...
    SyPy閱讀 2,584評論 4 11
  • 要點(diǎn): 函數(shù)式編程:注意不是“函數(shù)編程”,多了一個(gè)“式” 模塊:如何使用模塊 面向?qū)ο缶幊蹋好嫦驅(qū)ο蟮母拍睢傩浴?..
    victorsungo閱讀 1,707評論 0 6
  • Python進(jìn)階框架 希望大家喜歡,點(diǎn)贊哦首先感謝廖雪峰老師對于該課程的講解 一、函數(shù)式編程 1.1 函數(shù)式編程簡...
    Gaolex閱讀 6,038評論 6 53
  • 老王坐在桌子旁,有所思地望著這覆著銅銹的鏡子。 老王知道這面鏡子價(jià)值萬元,自從它從院中的那口枯井里被意外挖出時(shí),第...
    索風(fēng)閱讀 340評論 0 2

友情鏈接更多精彩內(nèi)容