裝飾器

裝飾器(Decorator)

裝飾器推導過程

  • 需求
    • 一個加法函數,想增強它的功能,能夠輸出被調用過以及調用的參數信息
      def add(x,y):
          return x + y
      
      • 增加信息輸出功能
        def add(x,y):
            print('call {},{}+{}'.format(add.__name__, x, y))
            return x + y
        

        59e4a060da105f7aef000006

    • 不足:
      • 打印語句的耦合太高
      • 加法函數屬于業務功能,而輸出信息的功能,屬于非業務功能代碼,不該放在業務函數加法中
    • 業務功能分離
      def add(x,y):    return x+ydef logger(fn):    print('begin')  #增強的輸出    ret = fn(4,7)    print('end')    #增強的功能    return retlogger(add)

      59e55c9f69daed201d000002

      • 但是fn函數傳參是一個問題

        59e55cd769daed201d000003

    • 解決傳參
      def add(x,y):    return x+ydef logger(fn,*args, **kwargs):    print('begin')      ret = fn(*args, **kwargs)    print('end')        return retlogger(add,7,9)

       

      • 但又想進一步的簡化
    • 柯里化
      def add(x,y):
          return x+y
      
      def logger(fn):
          def wrapper(*args, **kwargs):
              print('begin')  
              ret = fn(*args, **kwargs)
              print('end')    
              return ret
          return wrapper
      
      logger(add)(6,5)
      
      • 調用可以換一種寫法
        add = logger(add)
        add(6,5)
        

      59e55d5569daed201d000005

    • 裝飾器語法糖
      def logger(fn):    def wrapper(*args, **kwargs):        print('begin')          x = fn(*args, **kwargs)        print('end')            return x    return wrapper```@logger  #等價于 add = logger(add)```def add(x,y):    return x+yadd(6,43)

      59e55d8d69daed201d000006


無參裝飾器

  • 裝飾器(無參)
    • 它是一個函數
    • 函數作為它的形參
    • 返回值也是一個函數
    • 可以使用@functionname方式,簡化調用
  • 裝飾器和高階函數
    • 裝飾器是高階函數,傳入和輸出都是函數。
    • 但裝飾器是對傳入函數的功能裝飾(功能增強)

應用

獲取函數的執行時長,對時長超過閾[yù]值的函數記錄一下

import datetime
import time


def logger(fn):
    def wrapper(*args, **kwargs):

        start = datetime.datetime.now()

        print('begin')  
        ret = fn(*args, **kwargs)
        print('end')    

        end = (datetime.datetime.now() - start).total_seconds()
        print('function {} took {}s'.format(fn.__name__,end))

        return ret
    return wrapper

@logger  
def add(x,y):
    print('===call add=========')
    time.sleep(3)
    return x+y

59e570d569daed201d00000b


文檔字符串

  • python的文檔
    • python是文檔字符串Documentation Strings
    • 在函數語句塊的第一行,且習慣是多行的文本,所以多使用三引號
    • 慣例是首字母大寫,第一行寫概述,空一行,第三行寫詳細描述
    • 可以使用特殊屬性doc訪問這個文檔
def add(x,y):
    """
    This is a function of addition

    int x
    int y
    return int
    """
    a = x+y
    return x + y

print("name={}\ndoc={}".format(add.__name__, add.__doc__))
print(help(add))

59e559ce69daed201d000000


文檔字符串應用

凡是被裝飾的函數都需要復制這些屬性,這個函數很通用

  • 副作用
    def logger(fn):
        def wrap(*args,**kwargs):
            '''
            This is a wrapper
            '''
            print('begin')
            ret = fn(*args,**kwargs)
            print('end')
            return ret
        return wrap
    
    @logger
    def add(x,y):
        '''
        This is a function of addition
    
        int x
        int y
        return int
        '''
        return x + y
    
    print('name = {}, doc = {}'.format(add.__name__,add.__doc__))
    

    59e5610d69daed201d000007

    • 原函數對象的屬性都被替換了,而使用裝飾器,我們的需求是查看被封裝函數的屬性
    • 也就是想看到add的屬性,結果顯示的是wrap的
    • 如何解決?

  • 提供一個函數,被封裝函數屬性 copy 成 包裝函數屬性
    def copy_properties(src,dst):
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
    
    def logger(fn):
        def wrap(*args,**kwargs):
            '''
            This is a wrapper
            '''
            print('begin')
            ret = fn(*args,**kwargs)
            print('end')
            return ret
        copy_properties(fn,wrap)
        return wrap
    
    @logger
    def add(x,y):
        '''
        This is a function of addition
    
        int x
        int y
        return int
        '''
        return x + y
    
    print('name = {}, doc = {}'.format(add.__name__,add.__doc__))
    

    59e5661569daed201d000008


  • copy_properties可以改造成裝飾器
    def copy_properties(src):    def _copy(dst):        dst.__name__ = src.__name__        dst.__doc__ = src.__doc__        return dst    return _copydef logger(fn):    @copy_properties(fn)    def wrap(*args,**kwargs):        '''        This is a wrapper        '''        print('begin')        ret = fn(*args,**kwargs)        print('end')        return ret    return wrap@loggerdef add(x,y):    '''    This is a function of addition    int x    int y    return int    '''    return x + yprint('name = {}, doc = {}'.format(add.__name__,add.__doc__))#第9行等價于 wrap = copy_properties(fn)(wrap)

    59e5698069daed201d000009


帶參裝飾器

  • 它是一個函數
  • 函數作為它的形參
  • 返回值是一個不帶參的裝飾器函數
  • 使用@functionname(參數列表)方式,簡化調用
  • 可以看做在裝飾器外層又加了一層函數

應用

獲取函數的執行時長,對時長超過閾[yù]值的函數記錄一下

import datetime
import time

def logger(t1,t2):
    def _logger(fn):
        def wrapper(*args, **kwargs):

            start = datetime.datetime.now()

            print('begin')  
            ret = fn(*args, **kwargs)
            print('end')  

            delta = (datetime.datetime.now() - start).total_seconds()
            if delta > t1 and delta < t2:
                print('function {} took {}s'.format(fn.__name__,delta)

            return ret
        return wrapper
    return _logger

@logger(3,5) 
def add(x,y):
    print('===call add=========')
    time.sleep(6)
    return x+y

59e575c269daed201d00000f


  • 將記錄的功能提取出來,這樣就可以通過外部提供的函數來靈活的控制輸出
    import datetime
    import time
    
    def logger(duration, func = lambda name,t1: print('{} took {}s'.format(name,t1))):
        def _logger(fn):
            def wrapper(*args, **kwargs):
    
                start = datetime.datetime.now()
    
                print('begin')  
                ret = fn(*args, **kwargs)
                print('end')  
    
                delta = (datetime.datetime.now() - start).total_seconds()
                if delta > duration:
                    func(fn.__name__, delta)
    
                return ret
            return wrapper
        return _logger
    
    @logger(3) 
    def add(x,y):
        print('===call add=========')
        time.sleep(6)
        return x+y

59e573ed69daed201d00000e

本文來自投稿,不代表Linux運維部落立場,如若轉載,請注明出處:http://www.www58058.com/88024

(0)
nolannolan
上一篇 2017-10-23 08:54
下一篇 2017-10-23 10:35

相關推薦

  • 軟件包管理

    1.程序包管理器 源代碼–>目標二進制格式–>組織稱為一個或有限幾個“包”文件;     安裝、升級、卸載、查詢、校驗 程序包管理器: debian(Ubuntu):dpt,工具:dpkg,程序包以“.deb”結尾 redhat:redhat package manager&nbsp…

    Linux干貨 2016-08-21
  • PHP數組實際占用內存大小的分析

    我們在前面的php高效寫法提到,盡量不要復制變量,特別是數組。一般來說,PHP數組的內存利用率只有 1/10, 也就是說,一個在C語言里面100M 內存的數組,在PHP里面就要1G。下面我們可以粗略的估算PHP數組占用內存的大小,首先我們測試1000個元素的整數占用的內存: <?php      &nb…

    系統運維 2015-05-28
  • PHP字符串的編碼問題

        大家都知道,不同字符編碼,其在內存占用的字節數不一樣。如ASCII編碼字符占用1個字節,UTF-8編碼的中文字符是3字節,GBK為2個字節。     PHP 也自帶幾種字符串截取函數,其中常用到的就是 substr 和 mb_substr。     使用substr截取中…

    Linux干貨 2015-10-22
  • 初識shell腳本編程

    shell的編程三種分類方式介紹,如何使用nano命令編寫一個腳本命令,及腳本命令運行的2種方式。bash的配置文件及系統啟動時加載配置文件的順序流程

    2017-12-14
  • 網卡和路由的設置

    一:設置網卡別名: 首先,關閉NetworkManager服務 service NetworkManager stop 創建一個配置文件 ,文件名為網卡的文件名+“:num”如 : 網卡文件為:ifcfg-eth0 ,創建文件“ifcfg-eth0:1",冒號后跟的數字,可以隨意指定。 1,關閉NetworkManager服務   [ro…

    Linux干貨 2016-09-12
欧美性久久久久