Python修飾器簡介

原文來自簡書,地址:https://www.jianshu.com/p/ab702e4d4ba7

 

前言

對python的修飾器的理解一直停留在"使用修飾器把函數註冊爲事件的處理程序"的層次,也是一知半解;這樣拖着不是辦法,索性今天好好整理一下關於python修飾器的概念及用法。

介紹

裝飾器是一個很著名的設計模式,經常被用於有切面需求的場景,較爲經典的有插入日誌、性能測試、事務處理等。裝飾器是解決這類問題的絕佳設計,有了裝飾器,我們就可以抽離出大量函數中與函數功能本身無關的雷同代碼並繼續重用。概括的講,裝飾器的作用就是爲已經存在的對象添加額外的功能。

功能

我們首先從一個簡單的例子說起,這個例子是stackflow上的一個問題,如何通過使用如下的代碼實現輸出<b><i>Hello</i></b>:

@makebold  
@makeitalic  
def say():  
   return "Hello"

先看一下答案:

def makebold(fn):  
    def wrapped():  
        return "<b>" + fn() + "</b>"  
    return wrapped  
   
def makeitalic(fn):  
    def wrapped():  
        return "<i>" + fn() + "</i>"  
    return wrapped  
  
@makebold  
@makeitalic  
def hello():  
    return "hello world"  
   
print hello() ## 返回 <b><i>hello world</i></b> 

這裏的@makebold@makeitalic似乎給Hello加上了一層包裝(or修飾),這就是修飾器最明顯的體現。

從需求談起

初期,我寫了一個函數

def foo():  
    print 'in foo()'  
foo() 

爲了檢查這個函數的複雜度(在網絡編程中程序的延時還是很重要的),需要測算運算時間,增加了計算時間的功能有了下面的代碼:

import time  
def foo():  
    start = time.clock()  
    print 'in foo()'  
    end = time.clock()  
    print 'Time Elapsed:', end - start  
   
foo()  

這裏只是寫了一個函數,如果我想測量多個函數的延時,由於必須知道start與end,所以必須寫在程序的開頭與結尾,難道每一個程序都這樣複製粘貼麼?固然可行,但是,我們可以通過設計模式中將功能與數據部分分離一樣,將這個測量時間的函數分離出去,就像C++中我們可以將這個測量時間的函數變爲一個類,通過調用這個類,賦予不同的函數來測量不同的函數的運行時長。在python中,由於函數實際上就是對象,所以可以利用類似的方法實現:

import time  
   
def foo():  
    print 'in foo()'  
   
def timeit(func):  
    start = time.clock()  
    func()  
    end =time.clock()  
    print 'Time Elapsed:', end - start  
   
timeit(foo)  

這裏func()就可以指定函數了,但是如果我不想填這個函數或者這個功能函數並不能修改成類似的形式怎麼辦?我們需要的是最大限度的少改動:

import time  
   
def foo():  
    print 'in foo()'  
   
# 定義一個計時器,傳入一個,並返回另一個附加了計時功能的方法  
def timeit(func):  
       
    # 定義一個內嵌的包裝函數,給傳入的函數加上計時功能的包裝  
    def wrapper():  
        start = time.clock()  
        func()  
        end =time.clock()  
        print 'Time Elapsed:', end - start  
       
    # 將包裝後的函數返回  
    return wrapper  
   
foo = timeit(foo)   #可以直接寫成@timeit + foo定義,python的"語法糖"
foo()

在這個代碼中,timeit(foo)不是直接產生調用效果,而是返回一個與foo參數列表一致的函數,此時此foo非彼foo!因爲此時的foo具有了timeit的功效,簡單來說就是能夠讓你在裝飾前後執行代碼而無須改變函數本身內容,裝飾器是一個函數,而其參數爲另外一個函數。

一個有趣的"漢堡"讓你瞭解順序

順序在修飾器還是非常重要的,利用一個代碼展示一下:

def bread(func) :  
    def wrapper() :  
        print "</'''       '''\>"  
        func()  
        print "<\______/>"  
    return wrapper  
   
def ingredients(func) :  
    def wrapper() :  
        print "#tomatoes#"  
        func()  
        print "~salad~"  
    return wrapper  
   
def sandwich(food="--ham--") :  
    print food  
   
sandwich()  
#輸出 : --ham--  
sandwich = bread(ingredients(sandwich))  
sandwich()  

#輸出:  

#</'''       '''\>  
# #tomatoes#  
# --ham--  
# ~salad~  
#<\______/>  

加上語法糖,代碼可以更簡潔:

def bread(func) :  
    def wrapper() :  
        print "</'''       '''\>"  
        func()  
        print "<\______/>"  
    return wrapper  
   
def ingredients(func) :  
    def wrapper() :  
        print "#tomatoes#"  
        func()  
        print "~salad~"  
    return wrapper  

@bread  
@ingredients  
def sandwich(food="--ham--") :  
    print food  
   
sandwich()

拓展

內置修飾器

內置的裝飾器有三個,分別是staticmethod、classmethod和property,作用分別是把類中定義的實例方法變成靜態方法、類方法和類屬性。

對有參函數進行修飾

一個參數

如果原函數有參數,那閉包函數必須保持參數個數一致,並且將參數傳遞給原方法

def w1(fun):
    def wrapper(name):
        print("this is the wrapper head")
        fun(name)
        print("this is the wrapper end")
    return wrapper

@w1
def hello(name):
    print("hello"+name)

hello("world")

# 輸出:
# this is the wrapper head
# helloworld
# this is the wrapper end

多個參數測試:

def w2(fun):
    def wrapper(*args,**kwargs):
        print("this is the wrapper head")
        fun(*args,**kwargs)
        print("this is the wrapper end")
    return wrapper

@w2
def hello(name,name2):
    print("hello"+name+name2)

hello("world","!!!")

#輸出:
# this is the wrapper head
# helloworld!!!
# this is the wrapper end

有返回值的函數


def w3(fun):
    def wrapper():
        print("this is the wrapper head")
        temp=fun()
        print("this is the wrapper end")
        return temp   #要把值傳回去呀!!
    return wrapper

@w3
def hello():
    print("hello")
    return "test"

result=hello()
print("After the wrapper,I accept %s" %result)

#輸出:
#this is the wrapper head
#hello
#this is the wrapper end
#After the wrapper,I accept test

有參數的修飾器

直接上代碼:

def func_args(pre='xiaoqiang'):
    def w_test_log(func):
        def inner():
            print('...記錄日誌...visitor is %s' % pre)
            func()

        return inner

    return w_test_log


# 帶有參數的修飾器能夠起到在運行時,有不同的功能

# 先執行func_args('wangcai'),返回w_test_log函數的引用
# @w_test_log
# 使用@w_test_log對test_log進行修飾
@func_args('wangcai')
def test_log():
    print('this is test log')


test_log()

#輸出:
#...記錄日誌...visitor is wangcai
# this is test log

通用修飾器

對每個類型都有一個修飾器形式,怎麼記得下來?所以就有了這個"萬能修飾器":

def w_test(func):
    def inner(*args, **kwargs):
        ret = func(*args, **kwargs)
        return ret

    return inner


@w_test
def test():
    print('test called')


@w_test
def test1():
    print('test1 called')
    return 'python'


@w_test
def test2(a):
    print('test2 called and value is %d ' % a)


test()
test1()
test2(9)

# 輸出:
#test called
#test1 called
#test2 called and value is 9 

類修飾器

當創建一個對象後,直接去執行這個對象,那麼是會拋出異常的,因爲他不是callable,無法直接執行,但進行修改後,就可以直接執行調用:

class Test(object):
    def __call__(self, *args, **kwargs):
        print('call called')


t = Test()
print(t())
# 就可以直接執行

直接對類進行修飾:

class Test(object):
    def __init__(self, func):
        print('test init')
        print('func name is %s ' % func.__name__)
        self.__func = func

    def __call__(self, *args, **kwargs):
        print('this is wrapper')
        self.__func()


@Test
def test():
    print('this is test func')


test()

#輸出:
# test init
# func name is test 
# this is wrapper
# this is test func

後記

先介紹到這裏,大致也對修飾器有了一定的理解。後面自己會結合自己的項目繼續深入學習。

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