python裝飾器(Decorator)-從基本原理到實踐操作

Python的面向對象

python面向對象,函數也是對象

要理解裝飾器,首先明確python是面向對象的,函數也是對象。python中的函數也是對象,也可以像對象一樣來被操作,可以傳遞,可以賦值,可以刪除等等。

def speaking(words="Hello World"):
    print(words.capitalize()+'!')
speaking()
speaking(words='Good Morning')

#函數作爲一個對象,賦值給另外一個變量
talking=speaking()
talking
#也可以這樣來賦值
shouting=speaking
shouting(words="Shut up")

#可以被傳遞參數和賦值
def say(howToSay,what):
    howToSay(words=what)

say(speaking,"what")

def wish(HowtoSay):
    HowtoSay
wish(speaking())

輸出結果爲

Hello world!
Good morning!
Hello world!
Shut up!
What!
Hello world!

python中,一個函數可以定義在另外一個函數內部

python中,一個函數可以定義在另外一個函數內部

def wish():
    def speaking(words="Hello"):
        print(words.lower()+'!')
    speaking(words="Good")
    a=0
    print(a)
wish()

運行結果是

good!
0

這裏可以看到:

  • speaking()和a是局部變量,不可以被作用域外調用
  • python中的函數可以被賦值給另一個變量。
  • python中的函數可以非常靈活地在各種位置被定義,包括另一個函數內部。
    因而我們甚至可以把一個函數作爲另一個函數的返回值。如下面的例子所示。
def wish(return_type='morning'):
    def speaking(words="Good morning"):
        print(words.capitalize()+'!')
    def shouting(words="Yes"):
        print(words.lower()+' sir!')
    if return_type=='morning':
        return speaking
    else:
        return shouting

fun=wish('sir')
print(fun)
fun('No')

輸出爲

<function wish.<locals>.shouting at 0x00000240A9367048>
no sir!

同樣,函數可以作爲另外一個函數的輸入參數,如

def speaking(words="Good morning"):
    return words.capitalize() + '!'
def function_as_arguments(func):
    print("function as arguments")
    print(func)
function_as_arguments(speaking())

輸出結果爲:

function as arguments
Good morning!

什麼是裝飾器

本質而言,python中的裝飾器其實只是對其所裝飾的函數的一層額外包裝。其實現方法與商務的代碼邏輯類似,即接受一個函數作爲輸入,然後定義另外一個包裝函數在其執行前後加入另外一些邏輯,最終返回這個包裝函數。在裝飾器中,我們可以完全不修改原有函數的情況下,執行所裝飾的函數之外另外包裝一些別的代碼邏輯。裝飾器就是接受一個函數作爲輸入,並返回另外一個函數的函數。

一個基本的裝飾器

其基本邏輯爲:在一個裝飾器函數中,我們首先定義另外一個包裝函數,這個函數將負責在我們所要裝飾的函數前後文中添加我們需要的代碼邏輯(也就是將需要被裝飾的函數包裝起來)。然後在裝飾器函數中,我們將這一包裝函數作爲返回值返回。下面是一個簡單的裝飾器函數。

def basic_decorator(func_to_decorate):
    #定義包裝函數
    def the_wrapper_around_the_original_function():
        #被裝飾的原始函數執行之前的邏輯
        print("Before the original funcation runs")

        #調用原始函數
        func_to_decorate()

        #被裝飾的原始函數執行之後的邏輯
        print('After the original function runs')
    #返回當前裝飾器函數中動態定義的包裝函數
    return the_wrapper_around_the_original_function

下面展示如何使用這個裝飾器函數

def funtiong_wewantto_decorate():
    print('This is a function that is going to be decorated, we can add additional execution logic without changing the function')

funtiong_wewantto_decorate()
# 我們只需要將`funtiong_wewantto_decorate`作爲參數傳入我們上面定義的裝飾器函數中,就可以獲得一個被包裝過的新函數。
decorated_function=basic_decorator(funtiong_wewantto_decorate)
decorated_function()
print('-----***********')
#考慮到python中使用裝飾器往往是爲了在後文中完全用裝飾過後的函數替代我們原本定義的函數,我們可以將裝飾過後的函數賦值給原函數對應的變量名,從而在代碼下文中實現永久替換。
funtiong_wewantto_decorate=basic_decorator(funtiong_wewantto_decorate)
funtiong_wewantto_decorate()

運行結果如下:


This is a function that is going to be decorated, we can add additional execution logic without changing the function
Before the original funcation runs
This is a function that is going to be decorated, we can add additional execution logic without changing the function
After the original function runs
-----***********
Before the original funcation runs
This is a function that is going to be decorated, we can add additional execution logic without changing the function
After the original function runs

這就是裝飾器背後的邏輯,其表現和@註釋的裝飾器完全一樣。

用@標誌裝飾器

常用的標誌方法:

@basic_decorator
def funtiong_wewantto_decorate():
    print('This is a function that is going to be decorated, we can add additional execution logic without changing the function')

funtiong_wewantto_decorate()

運行結果:

Before the original funcation runs
This is a function that is going to be decorated, we can add additional execution logic without changing the function
After the original function runs

多個裝飾器的執行順序

進一步,可以對一個函數使用多個裝飾器。多層裝飾器將被從裏到外執行。即同一函數定義上方的裝飾器,最上面一行的裝飾器將被最後套用,而最下面一行的裝飾器將被最先套用。示例如下:

def decorator_func1(func):
    def wrapper1():
        print("decorator_func1 before")
        func()
        print("decorator_func1 after")
    return wrapper1
def decorator_func2(func):
    def wrapper2():
        print("decorator_func2 before")
        func()
        print("decorator_func2 after")
    return wrapper2

# func()
# decorator_func1(decorator_func2(func))
@decorator_func1
@decorator_func2
def bj(sentence="bj"):
    print(sentence)
bj()
decorator_func1 before
decorator_func2 before
bj
decorator_func2 after
decorator_func1 after

裝飾器的一個實際示例

如何使用裝飾器,或者用裝飾器能幹什麼。現在我們有兩個裝飾器,一個是能夠自動給字符串增加斜體HTML tag,一個是能夠自動增加黑體HTML tag。

def make_fold(func):
    def wrapper():
        return '<b>{}</b>'.format(func())
    return wrapper
#增加斜體tag的裝飾器
def make_italic(func):
    def wrapper():
        return '<i>{}</i>'.format(func())
    return wrapper

@make_fold
@make_italic
def say():
    return "hello world"
print(say())
#上面等同於
def say():
    return "hello world"
say=make_fold(make_italic(say))
print(say())

運行結果如下,好好體會一下。python的裝飾器本質上只是一個接受一個函數對象作爲輸入,在該函數前後增加一些新邏輯,然後返回包裝過後的新函數對象的對象。

<b><i>hello world</i></b>
<b><i>hello world</i></b>

裝飾器的進階

有輸入參數的函數的裝飾器

對於有輸入參數的函數,該如何定義適用於他們的裝飾器。

def decorator_for_func_with_arguments(func_to_decorate):
    def wrapper(arg1,arg2):
        print("good morning, {} {}".format(arg1,arg2))
        func_to_decorate(arg1,arg2)
    return wrapper

@decorator_for_func_with_arguments
def print_full_name(first_name,last_name):
    print("I am {} {}".format(first_name,last_name))
print_full_name("Jorden","Henderson")

輸出結果爲:

good morning, Jorden Henderson
I am Jorden Henderson

由此可見,裝飾器事實上返回了一個新的函數來代替我們需要裝飾的函數,所以只要確保在裝飾器中返回的新函數和原函數所接受的參數格式一致即可。

類方法的裝飾器

類方法的裝飾器,類方法事實上和函數一樣,只是固定接受當前實例的引用作爲第一個參數,即self。那麼能夠裝飾類放的裝飾器事實上也可以用與問題中一致的方法來實現,只不過要確保返回的函數所接受的第一個參數也是當前實例的引用self即可。

def decorator_for_instance_method(method_to_decorate):
    def wrapper(self,bonus):
        bonus=bonus*3
        print("before")
        method_to_decorate(self,bonus)
        print("after")

    return wrapper

class Salary(object):
    def __init__(self):
        self.base=1000
    @decorator_for_instance_method
    def total_compensation(self,bonus):
        print("the total compensation is {}".format(self.base+bonus))

salary_instance=Salary()
salary_instance.total_compensation(500)

運行結果如下:

before
the total compensation is 2500
after

類似,我們可以用python中的*args,**kwargs來實現一個能夠裝飾接受任意書目參數函數的裝飾器。

def decorator_passing_arbitrary_arguments(function_to_decorate):
    def wrapper_with_arbitrary_arguments(*args, **kwargs):
        print('Received arguments as following')
        print(args)
        print(kwargs)

        function_to_decorate(*args, **kwargs)

    return wrapper_with_arbitrary_arguments

@decorator_passing_arbitrary_arguments
def function_with_no_argument():
    print('This function does not have any argument')

function_with_no_argument()
# output:
# Received arguments as following
# ()
# {}
# This function does not have any argument

@decorator_passing_arbitrary_arguments
def function_with_arguments(a, b, c):
    print('This function has arguments')

function_with_arguments(1,2,3)
# output:
# Received arguments as following
# (1, 2, 3)
# {}
# This function has arguments

@decorator_passing_arbitrary_arguments
def function_with_named_arguments(a, b, c, name)
    print('{}, {}, {}'.format(a, b, c))
    print('{}'.format(name))

function_with_named_arguments(1, 2, 3, name='robot')
# output:
# Received arguments as following
# (1, 2, 3)
# {'name': 'robot'}
# 1, 2, 3
# robot

class Salary(object):
    def __init__(self):
        self.base = 666

    @decorator_passing_arbitrary_arguments
    def total_compensation(self, bonus):
        print('Congrats! You got a total compensation of {}'.format(self.base * 12 + bonus))

salary = Salary()
salary.total_compensation(2048)
# salary.total_compensation(2048)
# Received arguments as following
# (<__main__.Salary object at 0x1070b5f28>, 2048)
# {}
# Congrats! You got a total compensation of 10040

給裝飾器傳入參數

所謂裝飾器其實就是接收一個函數作爲輸入,並返回另一個函數的函數。這種情況下,由於裝飾器的函數簽名已經固定,所以我們無法直接傳入除輸入函數之外的參數。在無法直接改變裝飾器簽名的情況下,我們需要採用一些別的辦法來實現我們的目標——實現一個能夠返回裝飾器的函數來替裝飾器接收參數,並使用閉包的方法來將這些參數傳遞到裝飾器中。換句話說,我們需要一個裝飾器工廠函數來替我們動態生成裝飾器。

def decorator_maker():
    print("this is a factory generating decorators")
    def my_decorater(func):
        print("my decorator")
        def wrapper():
            print("this a wrapper")
            return func()
        return wrapper
    print("decorator created")
    return my_decorater

@decorator_maker()
def func():
    print("this is a function")

結果爲:

this is a factory generating decorators
decorator created
my decorator
this a wrapper
this is a function

裝飾器的最佳實踐

裝飾器的用法多種多樣。舉例來說,我們如果想要擴展一個第三方庫中附帶的函數的功能,但我們又無法修改該函數源代碼的時候,我們就可以使用裝飾器來實現這一目的。或者我們在debug的時候,爲了避免對源代碼進行多次修改,就可以用裝飾器來附加我們想要的邏輯。換句話說,我們可以用裝飾器實現所謂的“幹修改”(Dry Change)。實際上,python自身也提供了一些常用的裝飾器供大家調用,例如property,staticmethod,等等。與此同時,一些常用的python後端框架,例如Django及Pyramid也使用裝飾器來管理緩存以及視圖(view)訪問權限等。另外,裝飾器有時候也用來在測試中來虛構異步請求。

import time
import functools


def benchmark(func):
    """
    這是一個能夠計算並打印一個函數運行時間的裝飾器
    """
    def wrapper(*args, **kwargs):
        start_time = time.time()
        res = func(*args, **kwargs)
        end_time = time.time()
        print('{} completed in {} seconds'.format(func.__name__,  end_time - start_time))
        return res
    return wrapper


def logging(func):
    """
    這是一個能夠附加log功能的裝飾器
    """
    def wrapper(*args, **kwargs):
        res = func(*args, **kwargs)
        print('{} executed with args: {} and kwargs: {}'.format(func.__name__, args, kwargs))
        return res
    return wrapper


def counter(func):
    """
    這是一個能夠對函數被調用次數進行計數的裝飾器
    """
    def wrapper(*args, **kwargs):
        wrapper.count = wrapper.count + 1
        res = func(*args, **kwargs)
        print('{} has been called for {} times'.format(func.__name__, wrapper.count))
        return res
    wrapper.count = 0
    return wrapper


@c[添加鏈接描述](https://blog.csdn.net/Yaokai_AssultMaster/article/details/91552037)ounter
@logging
@benchmark
def reverse_string(string):
    return ''.join(reversed(string))


reverse_string('Tough times do not last, tough people do.')
# output:
# reverse_string completed in 3.814697265625e-06 seconds
# reverse_string executed with args: ('Tough times do not last, tough people do.',) and kwargs: {}
# reverse_string has been called for 1 times
# '.od elpoep hguot ,tsal ton od semit hguoT'

reverse_string('Two things are infinite: the universe and human stupidity; and I am not sure about the universe.')
# reverse_string completed in 5.9604644775390625e-06 seconds
# reverse_string executed with args: ('Two things are infinite: the universe and human stupidity; and I am not sure about the universe.',) and kwargs: {}
# reverse_string has been called for 2 times
# '.esrevinu eht tuoba erus ton ma I dna ;ytidiputs namuh dna esrevinu eht :etinifni era sgniht owT'

參考資料:
https://blog.csdn.net/Yaokai_AssultMaster/article/details/91552037
https://blog.csdn.net/Yaokai_AssultMaster/article/details/90730387

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章