1. 程式人生 > >Python學習—裝飾器

Python學習—裝飾器

技術 inf log 滿足 desc tel 註意 .... let

裝飾器

裝飾器實際上就是為了給某程序增添功能,但該程序已經上線或已經被使用,那麽就不能大批量的修改源代碼,這樣是不科學的也是不現實的,因為就產生了裝飾器,使得其滿足:
(1).不能修改被裝飾的函數的源代碼
(2).不能修改被裝飾的函數的調用方式
(3).滿足(1)、(2)的情況下給程序增添功能
實現:
我們寫一個嵌套函數,在內部函數中添加新功能新內容,然後調用原函數,再在外部函數return這個內部函數。由於函數也是一個對象,而且函數對象可以被賦值給變量,所以我們調用這個嵌套函數,把返回值(返回的是一個函數)賦給一個和原函數同名的變量,通過變量也能調用該函數。這樣就實現了不改變原函數代碼增強其功能。

舉個例子:

已經有一個函數login()實現的登陸功能,我想給它添加一些新內容:

def desc(fun):
    def add_info():
        print(‘寫在前面.......‘)
        fun()
        print(‘寫在後面........‘)
    return add_info

def login():
    print(‘login............‘)

login = desc(login)
login()

運行:
技術分享圖片

其實這就是一個裝飾器的雛形了。但是在使用這個裝飾器裝飾的時候,需要在每個函數前面加上這樣一句代碼:

    function = desc(function)

顯然有些麻煩,Python提供了一種語法糖來代替它:

@裝飾器名    #註意要把這句代碼放到原函數前

代碼修改如下,會時同樣的效果且更加簡潔:

def desc(fun):
    def add_info():
        print(‘寫在前面.......‘)
        fun()
        print(‘寫在後面........‘)
    return add_info

@desc
def login():
    print(‘login............‘)

login()

技術分享圖片

用裝飾器來實現計時功能:
計算在字符串連接時,+和join那個效率更高。

import random,string,time
li = [random.choice(string.ascii_letters) for i in range(1000)]    #這裏用了1000個字符來比較

def desc(fun):
    def wrapper():
        start_time = time.time()
        fun()
        end_time = time.time()
        print(‘%.8f‘ %(end_time-start_time))
    return wrapper

@desc
def a_add():
    st = ‘‘
    for i in li:
        st += (i+‘,‘)
    return st
@desc
def b_add():
    return ‘,‘.join(li)

a_add()
b_add()

運行:
技術分享圖片

顯然,此時join方式效率要高於連接符+
可以看到以上內容中函數沒有參數,即是無參函數裝飾器。

裝飾有參函數

用裝飾器來實現判斷變量類型:
對函數的傳入參數進行檢查,參數符合要求(整數)正常調用函數,不符合要求則報錯。

import functools   #導入functools包

def required_int(fun):
    @functools.wraps(fun)   #functools包中的wraps函數保證原函數的信息不因裝飾器而改變。
    def wrapper(*args):    #可變參數
        for i in args:
            if not str(i).isdigit():    #或者用isinstance(i,int)更加簡潔
                print(‘參數必須是整數...‘)
                return None
                break
        else:
            res=fun(*args)
            return res
    return wrapper

@required_int
def add_num(*args):   #參數為可變參數
    sum = 0
    for i in args:
        sum += i
    return sum

print(‘參數合法輸出結果:‘)
print(add_num(2,3,45,6))
print(‘參數不合法輸出結果:‘)
print(add_num(3,4,5,‘e‘))

運行:
技術分享圖片

模仿博客邏輯功能:
登陸後才能寫博客,瀏覽博客不需要登陸。

import functools

login_users = [‘admin‘,‘root‘]     #已經登陸的用戶
def is_login(fun):
    @functools.wraps(fun)
    def wrapper(*args,**kwargs):    #可變參數加關鍵字參數
        if kwargs.get(‘name‘) in login_users:
            res = fun(*args,**kwargs)
        else:
            res = login()
        return res
    return wrapper

def login():
    return ‘請先登陸.......‘

@is_login
def writtelog(name):
    return ‘寫博客日之.....‘

def viewnews():
     return ‘看文章新聞.....‘

print(writtelog(name=‘root‘))
print(writtelog(name=‘root11‘))
print(viewnews())

運行:
技術分享圖片

多個裝飾器的使用

模仿只有admin用戶在登陸後才能進入後臺:

import functools

login_users = [‘admin‘,‘root‘]     #已經登陸的用戶

def is_admin(fun):
    @functools.wraps(fun)
    def wrapper(*args,**kwargs):
        if kwargs.get(‘name‘)==‘admin‘:
            res = fun(*args,**kwargs)
        else:
            res = ‘沒有權限....‘
        return res
    return wrapper

def is_login(fun):
    @functools.wraps(fun)
    def wrapper(*args,**kwargs):
        if kwargs.get(‘name‘) in login_users:
            res = fun(*args,**kwargs)
        else:
            res = login()
        return res
    return wrapper

def login():
    return ‘請先登陸.......‘

@is_login
@is_admin
def houtai(name):
    return ‘進入後臺.....‘

print(houtai(name=‘admin‘))
print(houtai(name=‘root‘))
print(houtai(name=‘uuuu‘))

運行:
技術分享圖片

這裏需要註意的是,能用一個裝飾器盡量不要使用多個裝飾器。
當有多個裝飾器時,從上往下依次調用裝飾器,真實的wrapper內容時從上到下執行的。

帶參數的裝飾器

一個裝飾器,對不同的函數有不同的裝飾。那麽就需要知道對哪個函數采取哪種裝飾。因此,就需要裝飾器帶一個參數來標記一下。
將參數傳入,我們需要再加一層函數嵌套,來傳遞裝飾器的參數。
舉栗子:
對兩個不同的函數計時:

import time
def timer(parameter):    #參數parameter傳入調用的函數的名字
    def outer_wrapper(func):
        def wrapper(*args, **kwargs):
            if parameter == ‘task1‘:
                start_time = time.time()
                func(*args, **kwargs)
                stop_time = time.time()
                print("the task1 run time is :", stop_time - start_time)
            elif parameter == ‘task2‘:
                start_time = time.time()
                func(*args, **kwargs)
                stop_time = time.time()
                print("the task2 run time is :", stop_time - start_time)
        return wrapper
    return outer_wrapper

@timer(parameter=‘task1‘)
def task1():
    time.sleep(2)
    print("in the task1")

@timer(parameter=‘task2‘)
def task2():
    time.sleep(2)
    print("in the task2")

task1()
task2()

運行:
技術分享圖片

判斷函數傳入參數升級版本:

import functools,math

def required(*argss):
    def required_01(fun):
        @functools.wraps(fun)
        def wrapper(*args):
            for i in args:
                if not isinstance(i,argss):    #或者用isinstance(i,int)
                    print(‘參數必須是:‘,*argss)
                    return None
                    break
            else:
                res=fun(*args)
                return res
        return wrapper
    return required_01

@required(str,int)  #這裏可以看到要求傳入參數時str或者int
def add_num(*args):
    return args

print(add_num(‘ssss‘,789))    #符合要求
print(add_num(‘aaaa‘,693,3.14))     #有浮點型,不符合要求

運行:
技術分享圖片

Python學習—裝飾器