從淺到深講解python修飾器


本文為原創,轉載請注明出處

從淺到深講解python修飾器

什么是修飾器?

修飾器是一個函數,接受一個函數或方法作為其唯一的參數,並返回一個新函數或方法,其中整合了修飾后的函數或方法,並附帶了一些額外的功能.[1]

上面的定義不免有點難以理解,我們來看下面的圖

我們之前所理解的python執行函數過程是如圖1.1的流程.

如果我們給函數添加了修飾器,那么當程序執行到函數A的時候,系統會檢測到函數A上有一個修飾器,那么系統就會先執行修飾器里的過程然后再回到函數執行函數的內容.

但是從修飾器回來的那一條線路其實並不是必須的, 需要在修飾器里面編寫回來的語句回到函數,接下來會講到

修飾器的簡單作用

在其定義中就已經介紹修飾器的作用是給函數增加額外的功能.

: 在修飾器的簡單作用這一部分,接下來的內容我無法自己組織語言將其講清楚,故參考了簡書作者MrYun 談談python修飾器 的內容,個人覺得這一篇在引入修飾器作用方面的描寫很棒!

從簡單的一個例子講起,現在我們有一個函數

def foo():
    print("this is a test")

foo()

現在我們需要給它進行性能測試,那么需要改成以下內容

import time


def foo():
    start = time.clock()
    print("this is a test")
    end = time.clock()
    print("start:", start, " end:", end)


foo()

如果我們希望給幾十上百個函數都添加這樣的性能測試,那么需要在每個函數內部開頭與結尾都這樣編輯嗎?顯然不是.

這個時候我們編寫一個新的函數test(),在test的函數開頭與結尾編寫時間定義,將要測試的函數傳入test的函數(函數也是一個變量,是可以作為參數的),在中間執行,比如以下內容

import time


def foo():
    print("this is a test")


def test(func):
    start = time.clock()
    foo()
    end = time.clock()
    print("start:", start, " end:", end)


test(foo)

現在我們就可以給每個函數進行測試了

for 函數 in 項目:
    test(函數)

如果我們將test中的輸出加入到文件中,我們就可以得到每個函數的性能記錄

但是, 現在我們需要給大量函數實現另一個功能: 日志功能, 也就是在項目執行過程中, 函數的每一個操作都被記錄下來, 意味着每使用一次函數都要手動編寫test(foo), 尤其是如果需要使用函數的返回值的時候, 這種方式就有點捉襟見肘了

這個時候修飾器的作用就顯示出來了. 它可以在每個使用它的函數上進行功能的添加, 而且使用者完全感受不到他的存在, 也就是說我們使用的時候依然是foo(), 但是在內部項目卻另外實現了test()的功能

這個修飾器的使用格式如下,具體內容之后會講解

import time


def test(func):
    def wrapper():
        start = time.clock()
        func()
        end = time.clock()
        print("start:", start, " end:", end)
    return wrapper


@test
def foo():
    print("this is a test")


foo()

修飾器的使用

上面已經了解到了修飾器的作用,那么我們可以了解修飾器的格式了.

上面插入的修飾器太過突兀,我們來段過渡代碼(這一段代碼也是來自那篇簡書)

import time

def test(func):
    def wrapper():
        start = time.clock()
        func()
        end = time.clock()
        print("start:", start, " end:", end)
    return wrapper

def foo():
    print("this is a test")


foo = test(foo)
foo()

執行過程如下

在python中, 我們把foo = test(foo)用一種更簡單的形式來表達, 就是在使用裝飾器的函數foo上面加上 @修飾器名稱, python的修飾器是一種語法糖.

@test
def foo():
    print("this is a test")

如果需要使用到foo函數的返回值,那么test函數可以這樣寫

import time
def test(func):
    def wrapper():
        start = time.clock()
        print("this is a order test, if you need not it, delete it") # 用於測試執行順序,可以跟着走一遍
        a = func()
        end = time.clock()
        print("start:", start, " end:", end)
        return a # 這種獲得返回值的方法可能在多層修飾器的時候有矛盾,我先用!!!標記, 等理順后再回來修改,如果我發布之后這里依然存在...說明我忘記了...
    return wrapper

@test
def foo():
    print("this is a test")
    return "this is a return value"

print(foo())
# 輸出
# this is a test wrapper, if you need not it, delete it
# this is a test
# start: 4.44444839506524e-07  end: 1.8222238419767486e-05
# this is a return value

在《Python3程序開發指南第二版》(以下簡稱《指南》)中給的例子是一個對python初學者(沒涉及項目)來說比較有趣的小修飾器, 有興趣可以看看,我給它做了一點注釋

def positive_result(function):
    def wrapper(*args, **kwargs):
        # result獲得函數的返回值, 進行結果判斷
        result = function(*args, **kwargs)
        # assert斷言, 如果function函數的返回值大於等於0, 的產生一個AssertionError異常
        assert result >= 0, function.__name__ + "() result isn't >= 0"
        # 返回
        return result
    
    # 將wrapper的docstring和名稱設置成和原始函數一樣,有利於內省(獲得自身的信息)
    wrapper.__name__ = function.__name__ 
    wrapper.__doc__ = function.__doc__
    return wrapper

# 使用positive_result修飾器
@positive_result
def discriminant(a,b,c):
    return (b**2) - (4*a*c)


print(discriminant(0,6,5))
print(discriminant(2,6,5))

《指南》中給出了這個例子的簡化版, 使用到了functools.wraps(function)

def positive_result(function):
    # wrapper本身使用functools模塊的@functools.wraps進行包裹, 這可以確保wrapper的名稱與docstring與function相同
    @functools.wraps(function)
    def wrapper(*args, **kwargs):
        result = function(*args, **kwargs)
        assert result >= 0, function.__name__ + "() result isn't >= 0"

        return result
    return wrapper

修飾器參數化

現在我們已經了解到了什么是修飾器以及修飾器的基本使用, 那么在上面的日志修飾器上, 我們的日志信息往往是要寫入文件內,但是不同的函數需要寫進的文件名不一樣, 那么簡單的 @修飾器名稱已經沒法滿足需要了, 這個時候就需要修飾器參數化, 即將要操作的文件名傳遞給test()函數

現在放一個《指南》中給出的例子

import functools
def bounded(mininum, maxinum):
    def decorator(function):
        @functools.wraps(function)
        def wrapper(*args, **kwargs):
            result = function(*args, **kwargs)
            if result < mininum:
                return mininum
            elif result > maxinum:
                return maxinum
            return result
        return wrapper
    return decorator

@bounded(0,100)
def percent(amount, total):
    return (amount / total) * 100

percent(15,100)
  • 執行過程如下

(https://img2018.cnblogs.com/blog/1075950/201907/1075950-20190724200254507-881949016.png)

1. percent函數被執行, 檢測到有bounded修飾器, 

    ~~~py
    # 好忙啊啊啊啊啊,未完待續,如果你能看見這句話,,說明我忘記回來補了嚶嚶嚶
    ~~~

  1. 《Python3程序開發指南第二版》第八章P311 ↩︎


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM