Python-語法糖(裝飾器)


什么是高階函數?

       -- 把函數名當做參數傳給另外一個函數,在另外一個函數中通過參數調用執行

#!/usr/bin/python3

__author__ = 'beimenchuixue'
__blog__ = 'http://www.cnblogs.com/2bjiujiu/'


def func_x(x):
    return x * 2


def func_y(y):
    return y * 3


def func_z(x, y):
    # 等價於 return func_x(5) + func_y(3)
    return x(5) + y(3)

if __name__ == '__main__':
    # 把函數當做參數,本質上是把函數的內存地址當做參數傳遞過去,
    result = func_z(func_x, func_y)
    print(result)

 

什么是裝飾器?

  -- 在不改變源代碼的基礎上擴展新需求,裝飾器本身也是函數,應用高階函數實現

  -- 把被裝飾的函數內存地址當參數傳入裝飾器函數體,通過參數調用被裝飾的函數

       裝飾器原則:

    -- 不改變源代碼                         - 因為函數可能在其他地方各種調用,一改動全身

    -- 不改變原函數調用順序           - 源代碼有自己的邏輯處理

    -- 裝飾器又叫做語法糖

 

裝飾器邏輯上格式?

       - 高階函數+嵌套函數

    需求:

      給某個函數增加一個計算運行時間功能

#!/usr/bin/python3

__author__ = 'beimenchuixue'
__blog__ = 'http://www.cnblogs.com/2bjiujiu/'

import time


def total_time(func):                              # func = hell_word
    def wrapper():                                 # 等價於hell_word()
        start_time =time.time()
        func()
        end_time = time.time()
        print(end_time - start_time)               # 打印統計時間
    return wrapper


# 通過裝飾器給hell_word函數裝上了統計時間的功能,功能邏輯在裝飾器中實現
@total_time                                      
def hell_word():
    time.sleep(0.5)
    print('hello word')
    
if __name__ == '__main__':
    hell_word()

       相當於下面的函數邏輯

#!/usr/bin/python3

__author__ = 'beimenchuixue'
__blog__ = 'http://www.cnblogs.com/2bjiujiu/'

import time

# 裝飾器函數 def total_time(func): def wrapper(): start_time =time.time() func() end_time = time.time() print(end_time - start_time) return wrapper
def hell_word(): time.sleep(0.5) print('hello word') if __name__ == '__main__': # 把函數當做參數傳入裝飾器函數,然后裝飾器函數返回包裹函數wrapper地址,執行裝飾器函數本質上執行包裹函數wrapper中邏輯 total_time(hell_word)()

 

假如傳入的函數中有參數如何?

  -- 需要在wrapper和func中加入收集參數(*args)或收集字典參數(**kwargs),

  -- warps和func可自定義名字,默認如此命名

如果原函數有個返回值,該如何?

  -- 如果到func結束 直接在func()前面加return

  -- 到func未結束,可以func()結果賦值給一個變量,res = func(*args,**kwargs)到新增邏輯結束后加上 return res

 

需求:

  計算出斐波那契數列中第n個數的值?

  求一個共有10個台階的樓梯,從下走到上面,一次只能邁出1~3個台階,並且不能后退,有多少中方法?

  要求:

     通過裝飾器實現剪枝函數

如何邏輯整理這個需求? 

  斐波那契數列(黃金分割數列),從數列的第3項開始,每一項都等於前兩項之和

 

  每次邁出都是 1~3 個台階,剩下就是 7~9 個台階

               如果邁出1個台階,需要求出后面9個台階的走法

               如果邁出2個台階,需要求出后面8個台階的走法

               如果邁出3個台階,需要求出后面7個台階的走法

        此3種方式走法,通過遞歸方式實現,遞歸像樹,每次遞歸都生成子節點函數

#!/usr/bin/python3

__author__ = 'beimenchuixue'
__blog__ = 'http://www.cnblogs.com/2bjiujiu/'


def jian_zhi(func):
    # 中間字典,判斷已經是否求解過
    median = {}
    
    def wrapper(*args):
        # 假如不在中間字典中,說明沒有求解過,添加到字典中去,在的話,直接返回, 將不在遞歸下去,保證每次遞歸的唯一性
        if args not in median:
            median[args] = func(*args)
        return median[args]
    
    return wrapper


@jian_zhi
def fibonacci(n):
    if n <= 1:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)


@jian_zhi
def climb(n, steps):
    count = 0
    # 當最后台階為0的時候,說明最后只是走了一次
    if n == 0:
        count = 1
    # 當最后台階不為0的時候,說明還需要走至少一次
    elif n > 0:
        # 對三種情況進行分別處理momo
        for step in steps:
            count += climb(n - step, steps)
    
    # 返回每次遞歸的計數
    return count


if __name__ == '__main__':
    print(climb(10, (1, 2, 3)))
    print(fibonacci(20))

 

需求:

  實現在裝飾器函數中,保留 被裝飾函數 的元數據

那,什么是函數的元數據?

       在函數對象中保存着一些函數的元數據,如:

              f.__name__           函數名

              f.__doc__              函數文檔

              f.__moudle__       函數所屬模塊名

              f.__dict__              屬性字典

              f.__defaults__       默認參數組

              ……

       在使用裝飾器后,在裝飾器里訪問以上屬性時,我們看到的是裝飾器函數的元數據

那,如何解決這個需求?

  通過 functools中的wraps或update_wrapper方法實現,其中每個方法都可單獨實現

#!/usr/bin/python3

__author__ = 'beimenchuixue'
__blog__ = 'http://www.cnblogs.com/2bjiujiu/'

import time
from functools import (wraps, update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES)


def count_time(func):
    """
    給目標函數加上計算運行時間統計
    """
    
    # 這個裝上器和update_wrapper一樣,默認參數WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
    @wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.time()
        
        # 定義result接收函數返回值,並且在裝飾函數最后返回回去
        resutl = func(*args, **kwargs)
        print('運行時間:', time.time() - start_time)
        return resutl
    
    # 其中默認參數 WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES
    # update_wrapper(wrapper, func)
    return wrapper


@count_time
def add(num=100):
    """
    計算 0~num 累加值,默認num=100
    """
    time.sleep(1)
    return sum([x for x in range(num + 1)])


if __name__ == '__main__':
    print('函數名:', add.__name__)
    print('屬性字典:', add.__dict__)
    print('函數默認參數:', add.__defaults__)
    print('函數所在模塊:', add.__module__)
    print('函數文檔:', add.__doc__)
    
    # 打印兩個默認參數
    # WRAPPER_ASSIGNMENTS :__module__', '__name__', '__qualname__', '__doc__', '__annotations__
    # WRAPPER_UPDATES:__dict__
    print(WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES)
    result = add()
    print(result)

  

需求:

  實現一個裝飾器,用它來檢查被裝飾函數的參數類型,裝飾器可以通過函數,指明函數參數類型,進行函數調用的時候,傳入參數,檢測到不匹配時,拋出異常

那,如何解決這個需求?

  -- 先要獲取函數的簽名,並且獲得裝飾器中參數,然后把函數簽名和裝飾器中參數對應綁定

  -- 把調用函數時候傳入的參數和函數簽名進行綁定

  -- 把實參和裝飾器中定義的數據進行類型比較,不匹配拋出異常

#!/usr/bin/python3

__author__ = 'beimenchuixue'
__blog__ = 'http://www.cnblogs.com/2bjiujiu/'

from inspect import signature


def check_type(*ty_args, **ty_kwargs):
    def out_wrapper(func):
        # 通過signature方法,獲取函數形參:name, age, height
        sig = signature(func)
        # 獲得裝飾器傳來的參數, 函數簽名與之綁定,字典類型
        bind_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments
        print(bind_types)
        
        def wrapper(*args, **kwargs):
            # 給執行函數中具體的實參進行和形參進行綁定,形成字典的形式
            func_type = sig.bind(*args, **kwargs).arguments.items()
            print(func_type)
            # 循環形參和實參字典的items()形式
            for name, obj in func_type:
                if name in bind_types:
                    # 判斷實參是否是指定類型數據
                    if not isinstance(obj, bind_types[name]):
                        raise TypeError('%s must be %s' % (name, bind_types[name]))
            # 假如函數有返回值,通過此方法返回函數的返回值
            res = func(*args, **kwargs)
            return res
        
        return wrapper
    
    return out_wrapper


# 通過裝飾器實現對函數參數進行類型檢查
@check_type(str, int, float)
def func(name, age, height):
    print(name, age, height)


if __name__ == '__main__':
    # 正常數據
    func('bei_men', 18, 1.75)
    # 錯誤數據
    func('bei_men', '18', 1.75)

  

案例:

       為分析程序內哪些函數執行時間開銷較大,我們需定義一個帶timeout參數的裝飾器

       需求:

    統計被裝飾函數的運行時間

    時間大於timeout時,將此次函數調用記錄到log日志中

    運行時可以修改timeout的值

如何解決這個問題?

  1. 定義一個裝飾器,計算函數執行時間,並與timeout比較,當大於timeout時候,通過logging模塊打印出日志信息
  2. 在包裹函數中添加一個函數,通過這個函數來修改timeout變量
  3. 在python3中用nonlocal來聲明嵌套作用域中的變量引用,在python2中可以通過把timeout參數變成列表,通過列表索引來進行改值
    #!/usr/bin/python3
    
    __author__ = 'beimenchuixue'
    __blog__ = 'http://www.cnblogs.com/2bjiujiu/'
    
    
    import time
    import logging
    from random import randint
    
    
    def run_time(timeout):
        """
        定義檢查函數運行時間,並打印對應函數運行時間超出設定時間日志,並支持更改timeout
        """
        
        # python2
        # timeout = [timeout]
        
        # 真正包裹函數
        def out_wrapper(func):
            def wrapper(*args, **kwargs):
                start_time = time.time()
                result = func(*args, **kwargs)
                used_time = time.time() - start_time
                
                # 對於超出timeout的函數進行日志打印
                if used_time > timeout:
                    msg = '%s: %s > %s' % (func.__name__, used_time, timeout)
                    logging.warn(msg)
                    
                    # python2
                    # if used_time > timeout[0]:
                    #     msg = '%s: %s > %s' % (func.__name__, used_time, timeout[0])
                    #     logging.warn(msg)
                    # return result
                return result
            
            # 設置timeout參數值
            def set_timeout(value):
                # 聲明嵌套域變量,可以更改,python2通過把列表形式進行更改
                nonlocal timeout
                timeout = value
                
            # 定義接口
            wrapper.set_timeout = set_timeout
            
            # python2
            # def set_timeout(value):
            #     timeout[0] = value
            # wrapper.set_timeout = set_timeout
            
            return wrapper
        
        return out_wrapper
    
    
    @run_time(1.5)
    def func():
        # 隨機有50%的幾率程序沉睡1秒
        while randint(0, 1):
            time.sleep(1)
        print('func_run')
    
    
    if __name__ == "__main__":
        for _ in range(10):
            func()
        
        print('_' * 50)
        
        # 更改run_time裝飾器中timeout參數
        func.set_timeout(2)
        for _ in range(10):
            func()
    

      

如何邏輯整理?

  -- 3層 :一層獲得value,二層偷梁換柱,三層邏輯處理

  -- 2層:一層偷梁換柱,二層邏輯處理

 


免責聲明!

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



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