Python高階函數和裝飾器

高階函數

  • First Class Object
    • 函數在Python中是一等公民
    • 函數也是對象,可調用(callable)的對象
    • 函數可以作為普通變量、參數、返回值等等
  • 高階函數
    • 數學概念y=g(f(x))
    • 在Python中,高階函數應該滿足下列至少一個條件
      1. 接受一個或者多個函數作為參數
      2. 輸出一個函數
  • 計數器
    def counter(base):
        def inc(step=1):
            base += step
            return base
        return inc
    
    分析:
        - 函數counter是一個高階函數,因為它返回了一個函數對象
        - 上述函數的問題在于內層函數使用外層函數的本地變量,但同時有修改它,所以調用是會報錯UnboundLocalError,想要更改只需要使用關鍵字nonlocal在要改變前聲明它是外層函數的變量
        - 調用應該使用counter(base)(step)
        - f1 =counter(5)和f1 = counter(5)是不相等的,因為上述調用是兩次調用,這樣他們的ID就是不一樣的,所以不相等

自定義sort函數

  • 排序問題:仿照內建函數sorted,自行實現一個sort函數(不使用內建函數),能夠為列表元素排序
  • 思路
    • 內建函數sorted是返回一個新的列表,可以設置升序或降序,可以設置一個排序函數。自定義的函數也應該有類似功能
    • 新建一個列表,遍歷原列表,和新列表的值依次比較決定如何插入到新列表中
  • 函數實現1。判斷以下代碼是如何排序,還能優化么?
    def sort(iterable):
        ret = []
        for x in iterable:
            for i,y in enumerate(ret):
                if x > y:       #找到大的就插入。如果換成x<y,函數就是另一種排序
                    ret.insert(i,x)     #降序
                    break
        else:       #不大于說明是最小的,尾部追加
            ret.append(x)
        return ret 
    
    print(sort([1,7,2,9,3,6,8,4,5]))
  • sort函數實現2。用一個參數控制順序
    def sort(iterable,reverse=False):
        ret = []
        for x in iterable:
            for i,y in enumerate(ret);
                flag = x > y if reverse else x < y
                if flag:
                    ret.insert(i,x)
                    break
            else:
                ret.append(x)
        return ret
        
    print(sort([1,7,2,9,3,6,8,4,5]))
  • sort函數實現3。
    def sort iterable,fn=lambda a,b:a>b):
        ret = []
        for x in iterable:
            for i,y in enumerate(ret);
                if fn(i,x):     #函數返回值是bool型
                    ret.insert(i,x)
                    break
            else:
                ret.append(x)
        return ret
    
    print(sort([1,7,2,9,3,6,8,4,5]))

內建函數-高階函數

  • sorted(iterable[,key][,reverse])排序
    1. 返回一個新列表1,對一個可迭代對象的所有元素排序,排序規則為可以定義的函數,reverse表示是否排序翻轉
    2. sorted(lst,key=lambda x:6-x) #返回新列表
    3. list.sort(key=lambda x:6-x) #就地修改
  • filter(function,iterable)
    1. 過濾可迭代對象的元素,返回一個迭代器
    2. function是一個具有一個參數的函數,返回bool
    3. 例如,過濾除被3整除的數字:list(filter(lambda x:x%3 == 0,[1,9,55,150,-3,78,28,123]))
  • map(function,*iterables) –> map object
    1. 對多個可迭代對象的元素按照指定的函數進行映射,返回一個迭代器
    2. 例子:
      • list(map(lambda x:2*x+1,range(5)))
      • dict(map(lambda x:(X%5,x),range(500)))

柯里化

  • 指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數為參數的函數
  • z = f(x,y)轉換成z = f(x)(y)的形式
  • 舉例
    def add(x,y):
        return x+y
    
    轉換如下:
    def add(x):
        def _add(y):
            return x+y
        return _add
        
    add(5)(6)
    
    通過嵌套函數就可以吧函數轉換稱柯里化函數

裝飾器

  • 需求
    • 一個加法函數,想要加強它的功能,能夠輸出被調用過以及調用的參數信息
      def add(x,y):
          return x+y
      
      增加信息輸出功能
      def add(x,y):
          print('call add,{}+{}'.format(x,y))
          return x+y
    • 上面的加法函數是完成了需求,但是有以下缺點:
      1. 打印語句的耦合太高
      2. 加法函數屬于業務功能,而輸出信息的功能,屬于非業務功能代碼,不應該放在業務函數內部
  • 推演過程
    首先將業務函數和增強功能分開,但發現fn函數調用傳參是個問題
    def add(x,y):
        return x + y
    
    def logger(fn):
        print('begin')
        x = fn(4,5)
        print('end')
        return x
    
    print(logger(add))
    
    解決傳參的問題
    def add(x,y):
        return x + y
    
    def logger(fn,*args,**kwargs):    #這里*args和**kwargs代表可變參數
        print('begin')
        x = fn(*args,**kwargs)      #*args,**kwargs在這表示參數解構
        print('end')
        return x
    
    print(logger(add,5,y=50))
    
    柯里化
    def add(x,y):
        return x + y
    
    def logger(fn):
        def _logger(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        return _logger
    
    print(logger(add)(5,y=50))  
    #最后也可以這么寫
    # add = logger(add)
    # print(add(x=5,y=10))
  • 裝飾器語法
def logger(fn):
    def _logger(*args,**kwargs):
        print('begin')
        x = fn(*args,**kwargs)
        print('end')
        return x
    return _logger
    
@logger #等價于add= logger(add),這就是裝飾器語法
def add(x,y):
    return x+y
    
print(45,50)
  • 上面的語法是無參裝飾器
    1. 它是一個函數
    2. 函數作為他的形參
    3. 返回值也是一個函數
    4. 可以使用@functionname方式,簡化調用
  • 裝飾器是高階函數,但裝飾器對傳入函數的功能的裝飾(功能增強)

文檔字符串

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

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

使用裝飾器的副作用

  • 使用裝飾器后原函數對象的屬性都被替換了,而使用裝飾器,希望查看原函數的屬性,如何解決
    1. 可以自己定義一個函數將被包裝函數的屬性覆蓋掉包裝函數
    def copy_properties(src,dst):   #自定義復制屬性的函數
        dst.__name__ = src.__name__
        dst.__doc__ = src.__doc__
        
    
    def logger(fn):
        def _logger(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        copy_properties(fn,_logger) #函數調用,復制被包裝函數屬性覆蓋包裝函數屬性
        return _logger
    
    @logger 
    def add(x,y):
        return x+y
    
    print(45,50)
    1. 凡是被裝飾的函數都需要賦值這些屬性,這個函數很通用
    2. 可以將被復制屬性的函數構建成裝飾器函數,帶參裝飾器
    def copy_properties(src):   #柯里化
        def _copy(dst):
            dst.__name__ = src.__name__
            dst.__doc__ = src.__doc__
            return dst
        return _copy
    
    def logger(fn):
        @copy_properties(fn)    #_logger = copy_properties(fn)(_logger)
        def _logger(*args,**kwargs):
            print('begin')
            x = fn(*args,**kwargs)
            print('end')
            return x
        
        return _logger
    
    @logger 
    def add(x,y):
        """this is """        
        return x+y
    
    print(45,50)

帶參裝飾器

  • 它是一個函數
  • 函數作為他的形參
  • 返回值是一個不帶參的裝飾器函數
  • 使用@functionname(參數列表)方式調用
  • 可以看作在裝飾器外層又加了一層函數
def logger(duration):
    def _logger(fn):
        def wrapper(*args,**kwargs):
            start = datetime.datetime.now()
            ret = fn(*args,**kwargs)
            delta =(datetime.datetime.now()-start).total_seconds()
            print('so slow') if delta >duration else print('so fast')
            return ret
        return wrapper
    return _logger

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

functools模塊

  • functools.update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)
    • 類似copy_properties功能
    • wrapper包裝函數,wrapped被包裝函數
    • 元組WRAPPER_ASSIGNMENTS中是要被覆蓋的屬性
    • 元組WRAPPER_UPDATES中是要被更新的屬性,__dict__屬性字典
    • 增加一個__wrapped__屬性,保留著wrapped函數
  • functools.wraps(wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)和上個方法用法基本相同

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

(0)
KX_ilKX_il
上一篇 2017-10-22
下一篇 2017-10-23

相關推薦

  • Linux啟動流程

    內核級別: 1、POST開機加電自檢 BIOS加載CMOS信息并進行開機自檢,檢測硬件設備是否正常并進行硬件設備初始化 2、Boot Sequence(BIOS):定義設備啟動順序 BIOS定義設備啟動順序,并讀取啟動設備數據加載硬盤上的第一個扇區MBR。 3、Boot Loader(MBR):bootloader功能實現 Boot Loader提供一個菜單…

    Linux干貨 2016-11-23
  • N26第二周博客作業

    1.Linux上的文件管理類命令都有哪些,其常用的使用方法及其相關示例演示。 ?  ls 查看文件與目錄 ls [選項] 文件或目錄 常用選項 -a:全部的文件,連同隱藏文件(以.開頭)一起列出來 -d:列出目錄本身,而不是列出目錄里的文件 -l:列出詳細信息,如文件的屬性與權限等數據 -h:將文件容量以易讀的方式顯示(例如:GB,KB等) -t:…

    Linux干貨 2017-01-08
  • linux 刪除內核文件,未能啟動,修復方法 CDROM與網絡法

    當缺少 /boot 內核文件 vmlinuz-2…. initramfs-2… 此時只能借助 光盤 CDROM 來引導生成    進入  RESCUE 模式 1    chroot       &…

    Linux干貨 2016-09-13
  • 虛擬網卡實驗:網卡別名 與 bond多網卡模式

     一、網卡別名:多IP 綁定一個 Interface        ? 將多個IP地址綁定到一個NIC上            網卡別名:    &n…

    Linux干貨 2016-09-05
  • 用戶及權限管理 (Blog 1)

    用戶、權限相關概念及用戶管理類命令

    2017-11-06
  • btrfs管理及應用

    一. 簡介     btrfs有著強大的功能,它支持在多個及各種物理設備(包括RAID)上創建一個文件系統,并支持動態擴展或減小,支持快照功能,甚至快照的快照,支持子卷功能。 二. 如何使用btrfs文件系統?     我的準備材料:3塊20G的硬盤 [root@localhost ~]#…

    Linux干貨 2015-12-07
欧美性久久久久