Python進階(一) 裝飾器

裝飾器@ 在閱讀源碼時經常遇到,所以有必要弄明白!

概括的講,裝飾器的作用就是爲已經存在的對象添加額外的功能;

本質上,裝飾器就是一個返回函數的高階函數。其接受一個函數作爲參數,並返回一個函數。 藉助Python的@語法,把裝飾器置於函數的定義處,實現功能擴展的目的;

(一)需要用裝飾器的原因

先來看一個例子:

def f0():
    print('11111111')

f0()

這個函數的功能是打印出一竄字符竄。如果想要測試執行這個函數用了多長時間,我們可以這樣做:

import time

def f0():
    start = time.clock()
    print('11111111')
    end = time.clock()
    print('used:', end - start)

f0()

 這樣能夠很好的達到目的。但是想測試一個模塊的所有函數的執行時間呢,就得把所有函數中都加入如上時間差的計算方法,這樣不太現實。

爲了不改變原來的函數,我們可以定義一個函數timeit,將f0()的引用傳遞給他,然後在timeit中調用f0並進行計時,這樣我們就不用修改f0函數而達到目的了:

import time

def f0():
    print('11111111')

def timeit(fc):
    start = time.clock()
    fc()
    end = time.clock()
    print( 'used:', end - start)

timeit(f0)

這樣看上去邏輯沒有問題,而且可以正常的工作。但卻修改了調用部分的代碼,原本是f0()調用,現在卻成了timeit(f0),如果f0在很多處都被調用了,就需要在很多處修改代碼。注:這也是接下來的代碼裏,在timeit()函數裏需要再定義一個wrapper()函數的原因.

如果不改動調用的代碼,也就意味着調用f0()需要產生timeit(f0)的效果。我們可以這樣做,把timeit(f0)的返回值付給f0,然後直接調用f0(),就不用修改源代碼了:

import time

def f0():
    print('111111111111')

def timeit(fc):
    def wrapper():
        start = time.clock()
        fc()
        end = time.clock()
        print('used:', end - start)

    return wrapper

f0 = timeit(f0)
f0()

 這樣,我們需在定義f0以後和調用f0之前,加上f0=timeit(f0),就可以達到目的了。這就是修飾器,看起來像f0被timeit修飾了。

上面的代碼,看似沒法再精簡了,python於是提供了一個特殊的語法來降低字符輸入量:

import time

def timeit(fc):
    def wrapper():
        start = time.clock()
        fc()
        end = time.clock()
        print('used:', end - start)

    return wrapper

@timeit
def f0():
    print('11111111')

f0()

在第12行的@timeit,效果和f0=timeit(f0)一樣,而且看上去更有修飾器的感覺。

這就是Python中修飾器的原理。

參考 https://www.tuicool.com/articles/6Z3Mbuj

(二)裝飾器使用方法

函數作爲一個對象:① 可以被賦值給其他變量,可以作爲返回值      ② 也可以被定義在另外一個函數內;

裝飾器分類:有參/無參

① 無參:用於生成一個新的裝飾器函數   

② 有參:先處理這個參數,再生成一個新的裝飾器函數,然後對其做裝飾;

按照裝飾器與函數各自是否有參,組合共4中:

① 無參的裝飾器 ,包裝無參的函數  —— 不需要針對參數進行處理和優化

def F(fc): #裝飾器F無參
    print ('2222222')
    return fc

@F
def f0(): #函數無參
    print('1111111')

f0()

等價於

def F(fc):
    print ('2222222')
    return fc

def f0():
    print('1111111')

f0 = F(f0)
f0()

② 無參的裝飾器 ,包裝有參的函數

def F(fc): #裝飾器無參
    def handle_args(*args, **kwargs): #處理傳入函數的參數
        print("1111111")
        fc(*args, **kwargs) #函數調用
        print("2222222")
    return handle_args

@F
def f1(a, b=2): #函數有參
    print(a, b)

f1(1)

等價於

def F(fc):
    def handle_args(*args, **kwargs):
        print("1111111")
        fc(*args, **kwargs)
        print("2222222")
    return handle_args

def f1(a, b=2):
    print(a, b)

f1 = F(f1)
f1(1)

③ 有參的裝飾器 ,包裝無參的函數

def F(arg): #裝飾器的參數arg
    print(arg)
    #最終被返回的函數
    def newDecorator(fc):
        print(fc)
        return fc
    return newDecorator

@F('11111111')
def f2(): #函數無參
    print('22222222')

f2()

等價於

def F(arg):
    print(arg)
    def newDecorator(fc):
        print(fc)
        return fc
    return newDecorator

def f2():
    print('22222222')

f2 = F('111111')(f2)
f2()

④ 有參的裝飾器 ,包裝有參的函數

def F(arg): #裝飾器參數
    def handle_func(fc):
        def handle_args(*args, **kwargs): #'*'返回list或tuple,'**'返回dict
            print('11111111')
            fc(*args, **kwargs)
            print('22222222')
            print(arg, fc, args, kwargs)
        return handle_args
    return handle_func

@F('123')
def f3(a, b=2): #函數參數
    print('3333333333')

f3(1, b=3)

等價於

def F(arg):
    def handle_func(fc):
        def handle_args(*args, **kwargs):
            print('11111111')
            fc(*args, **kwargs)
            print('22222222')
            print(arg, fc, args, kwargs)
        return handle_args
    return handle_func

def f3(a, b=2):
    print('3333333333')

f3 = F('123')(f3)
f3(1, b=3)

 

注:有3個內置的裝飾器 staticmethod, classmethod, property 。

參考:http://wklken.me/posts/2012/10/27/python-base-decorator.html

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