14高階函數_柯里化_裝飾器_functools_docstring

 

高階函數、柯里化、裝飾器、functools、文檔字符串

 

 

目錄

高階函數... 1

內建函數-高階函數... 3

currying柯里化:... 4

decorator裝飾器:... 4

裝飾器(無參):... 7

帶參裝飾器:... 9

functools... 12

文檔字符串:... 14

 

 

 

高階函數

函數:

first class object,函數在python中是一等公民;

函數也是對象,可調用的對象;

函數可以作爲普通變量、參數、返回值等;

 

高階函數

數學概念,y=g(f(x))

在數學和計算機科學中,高階函數應當是至少滿足下面一個條件的函數:

接受一個或多個函數作爲參數;

輸出一個函數,如return inc

 

計數器:

例:

In [1]: def counter(base):   #函數counter()是一個高階函數

   ...:     def inc(step=1):

   ...:         nonlocal base

   ...:         base += step   #賦值前引用,用nonlocal解決

   ...:         return base

   ...:     return inc

   ...:

In [2]: foo=counter(10)

In [3]: foo1=counter(10)

In [4]: foo==foo1   #foofoo1不一樣,可理解爲inccounter內的標識符,每次counter()運行是不同的對象

Out[4]: False

In [5]: id(foo)

Out[5]: 139964301851096

In [6]: id(foo1)

Out[6]: 139964302182600

In [7]: foo()

Out[7]: 11

In [8]: foo()

Out[8]: 12

In [9]: foo1()

Out[9]: 11

In [10]: foo1()

Out[10]: 12

 

自定義一個sort函數:

排序問題,參照內建函數sorted,自行實現一個sort函數(不使用內建函數),能夠爲列表元素排序;

需求:原列表不變(sorted(lst));原列表改變(lst.sort());

思路:

內建函數sorted返回一個新列表,可設置升序降序,可設置一個排序的函數,自定義sort函數也要具備這些功能;

新建一個列表,遍歷原列表,和新列表的值依次比較決定如何插入到新列表中;

sorted函數的實現原理,擴展到mapfilter函數;

 

例:

def sort(iterable):

    ret = []

    for x in iterable:

        for i,y in enumerate(ret):

            if x > y:

                ret.insert(i,x)

                break

        else:

            ret.append(x)

    return ret

 

sort([5,3,2,1,4])

#######################

def sort(iterable,reverse=False):

    ret = []

    for x in iterable:

        for i,y in enumerate(ret):

            flag = x < y if reverse else flag = x > y

            if flag:

                ret.insert(i,x)   #找到大的就插入,降序

                break

        else:

            ret.append(x)   #小的尾部追加

    return ret

 

sort([5,3,2,1,4],reverse=True)

#######################

def sort(iterable,key=lambda a,b:a<b):   #默認值保存在__defaults__中,只是對該匿名函數對象的引用

    ret = []

    for x in iterable:

        for i,y in enumerate(ret):

            if key(x,y):

                ret.insert(i,x)

                break

        else:

            ret.append(x)

    return ret

 

sort([5,3,2,1,4],lambda a,b:a>b)

#########################

 

 

 

內建函數-高階函數

sorted(iterable[,key][,reverse])-->new list,排序,返回一個新列表,對一個可迭代對象的所有元素排序,排序規則爲key定義的函數,可用reverse指定排序翻轉;

例:

In [11]: lst = [3,2,4,5,1]

In [12]: sorted(lst,key=lambda x:6-x)   #內置sortedkey可對每個元素進行變化,該變化只在比較時用於臨時值,比較後的新列表中的元素仍然是原列表中的元素

Out[12]: [5, 4, 3, 2, 1]

In [13]: lst = ['a',1,2,3,'b']

In [15]: sorted(lst,key=str)   #key=str爲比較邏輯

Out[15]: [1, 2, 3, 'a', 'b']

 

filter(function,iterable)-->filter object,generator,過濾數據,過濾可迭代的元素,返回一個迭代器function是一個具有一個參數的函數,返回bool

例:

In [16]: filter(lambda x:x%3==0,[1,9,55,150,-3.78,28,123])

Out[16]: <filter at 0x7f4bf9d6f320>

In [17]: list(filter(lambda x:x%3==0,[1,9,55,150,-3.78,28,123]))   #過濾出列表中能被3整除的數字;注意function中要有參數x,否則報錯

Out[17]: [9, 150, 123]

 

map(function,*iterables)-->map object,generator,映射,對多個可迭代對象的元素按指定的函數進行映射,返回一個迭代器

例:

In [18]: map(lambda x:2*x+1,range(5))

Out[18]: <map at 0x7f4bf9d76080>

In [20]: list(map(lambda x:2*x+1,range(5)))   #可用此方式獲取奇數、偶數

Out[20]: [1, 3, 5, 7, 9]

In [21]: dict(map(lambda x:(x%5,x),range(500)))   #構造字典,通過key已去重(後面覆蓋前面的了)

Out[21]: {0: 495, 1: 496, 2: 497, 3: 498, 4: 499}

 

 

 

currying柯里化:

指將原來接受2個參數的函數變成新的接受一個參數的過程,新的函數返回一個以原有第二個參數爲參數的函數;

z=f(x,y)-->z=f(x)(y),這種形式;

例:

將加法函數柯里化:

In [22]: def add(x,y):

    ...:     return x+y

    ...:

In [23]: def add(x):

    ...:     def _add(y):

    ...:         return x+y

    ...:     return _add

    ...:

 

 

 

decorator裝飾器:

裝飾器的用途:

裝飾器是AOP思想的體現,aspect oriented programming面向切面編程;

面向對象往往需要通過繼承或組合依賴等方式調用一些功能,這些功能的代碼往往可能在多個類中出現,如logger,這樣造成代碼的重複,增加了耦合,logger的改變影響所有使用它的類或方法;

AOP在需要的類或方法上切下,前後的切入點可加入增強的功能,讓調用者和被調用者解耦;

這是一種不修改原來的業務代碼,給程序動態添加功能的技術,如logger函數功能就是對業務函數增加日誌的,而業務函數中應把與業務無關的日誌功能剝離乾淨;

 

裝飾器應用場景:

日誌、監控、權限、設計、參數檢查、路由等;

這些功能與業務功能無關,很多業務都需要公共功能,所以適合獨立出來,需要的時候對目標對象增強;

 

需求:

加法函數,增強其功能,輸出被調用過及調用的參數信息;

例:

def add(x,y):

    print('call: {},{}+{}'.format(add.__name__,x,y))

    return x+y

 

add(1,2)

注:

此方式完成了需求,但有缺點;

打印語句的耦合太高,與定義的函數緊緊關聯;

加法函數屬於業務功能,而輸出信息是非業務功能代碼,不該放在業務函數中,這稱爲侵入式代碼

##################

def add(x,y):

    return x+y

 

def logger(fn):

    print('begin')

    x=fn(4,5)

    print('end')

    return x

 

print(logger(add))

注:

雖做到了業務功能分離,但fn函數調用傳參是個問題;

##################

del add

 

def add(x,y,*args):

    return x+y

 

def logger(fn,x,y):

    print('begin')

    ret=fn(x,y)

    print('end')

    return ret

 

print(logger(add,4,5))

###################

def add(x,y,z):

    return x+y+z

 

def logger(fn,*args,**kwargs):   #可變參數,解決了傳參問題

    print('begin')

    ret=fn(*args,**kwargs)   #參數解構

    print('end')

    return ret

 

print(logger(add,4,z=5,y=6))

###################

def add(x,y,z):

    return x+y+z

 

def logger(fn):   #在上例基礎上將logger柯里化

    def _logger(*args,**kwargs):   #可變參數

        print('begin')

        ret=fn(*args,**kwargs)   #參數解構;閉包(fn爲自由變量)

        print('end')

        return ret

    return _logger   #返回內部函數的引用

 

print(logger(add)(4,5,6))

 

foo=logger(add)

print(foo(4,5,6))   #等價於print(logger(add)(4,5,6))

 

add=logger(add)   #等價於@logger

print(add(4,5,6))

#####################

def logger(fn):   #如果此處用兩個參數def logger(fn,x),後面使用@logger時會語法錯誤,解決辦法,進一步柯里化

    def _logger(*args,**kwargs):   #可變參數

        print('begin')

        ret=fn(*args,**kwargs)   #參數解構;此處閉包,正因爲閉包原函數在@logger(即add=logger(add))被保留下來;此處如果寫爲return ret=fn(*args,**kwargs)後面語句將不會執行

        print('end')

        return ret   #若無此行會破壞原函數

    return _logger   #外層函數應返回內層函數的引用

 

@logger   #等價於add=logger(add);第一個add已指向內層函數_logger一般起名爲wrapper包裝函數;第二個add(即logger(add))爲原函數;@logger要放到被包裝函數的上一行

def add(x,y):

    return x+y

 

print(add(4,5))   #此處add不是原函數,而是被包裝後的內層函數(即add=logger(add)

注:

外層函數應返回內層函數的引用;

外層函數的參數應爲要包裝的函數,add=logger(add)

@logger即裝飾器的語法;

 

 

 

裝飾器(無參):

它是一個函數;

函數作爲它的形參;

返回值也是一個函數;

可以使用@function方式,簡化調用;

 

裝飾器和高階函數:

裝飾器是高階函數,但裝飾器是對傳入函數的功能的裝飾(功能增強);

 

例:

import datetime

import time

 

def logger(fn):

    def wrapper(*args,**kwargs):

        print('args={},kwargs={}'.format(args,kwargs))

        start = datetime.datetime.now()

        ret = fn(*args,**kwargs)

        duration = (datetime.datetime.now()) - start

        print('function {} took {}s'.format(fn.__name__,duration.total_seconds()))

        return ret

    return wrapper

 

@logger

def add(x,y):

    print('=========call add===========')

    time.sleep(2)

    return x+y

 

print(add(4,y=5))

 

例:

def copy_properties(src,dst):

    dst.__name__ = src.__name__

    dst.__doc__ = src.__doc__

    dst.qualname__ = src.__qualname__

   

def logger(fn):

    def wrapper(*args,**kwargs):

        '''This is a wrapper'''

        print('begin')

        ret = fn(*args,**kwargs)

        print('end')

        return ret

    copy_properties(fn,wrapper)

    return wrapper

 

@logger

def add(x,y):

    '''

    This is a additional.

   

    return int

    x int

    y int

    '''

    ret = x + y

    return ret

 

print(add.__name__,add.__doc__,add.__qualname__,sep='\n')

#######################

def copy_properties(src):

    def wrapper(dst):

        dst.__name__ = src.__name__

        dst.__doc__ = src.__doc__

        dst.qualname__ = src.__qualname__

        return dst

    return wrapper

   

def logger(fn):

    @copy_properties(fn)   #等價於wrapper=copy_properties(fn)(wrapper),出現的wrapper可認爲都是logger內的wrappercopy_properties(fn)copy_properties內的wrapper

    def wrapper(*args,**kwargs):

        '''This is a wrapper'''

        print('begin')

        ret = fn(*args,**kwargs)

        print('end')

        return ret

    #copy_properties(fn,wrapper)

    return wrapper

 

@logger

def add(x,y):

    '''

    This is a additional.

   

    return int

    x int

    y int

    '''

    ret = x + y

    return ret

 

print(add.__name__,add.__doc__,add.__qualname__,sep='\n')

注:

包裝函數屬性改爲被包裝函數屬性;

調用時用到的是嵌套函數內層wrapper的屬性而不是原函數add的屬性,在使用copy_properties函數後,可讓使用者認爲是原函數的屬性;

通過copy_properties函數將被包裝函數的屬性覆蓋掉包裝函數;

凡是被裝飾的函數都要複製這些屬性,上例很通用;

可以將複製屬性的函數構建成裝飾器函數,帶參裝飾器;

 

 

 

帶參裝飾器:

是一個函數;

函數作爲它的形參;

返回值是一個不帶參的裝飾器函數(不一定);

使用@function(參數列表)方式調用;

可以看作在裝飾器外層又加了一層函數;

最多也就三層;

需求:

獲取函數的執行時長,對長超過閾值的函數記錄一下;

例:

import datetime

import time

 

def logger(t):

    def _logger(fn):

        def wrapper(*args,**kwargs):

            '''This is a wrapper.'''

            print('args={},kwargs={}'.format(args,kwargs))

            start = datetime.datetime.now()

            ret = fn(*args,**kwargs)

            duration = (datetime.datetime.now() - start).total_seconds()

            if duration > t:

                print('function {} took {}s'.format(fn.__name__,duration))

            return ret

        return wrapper

    return _logger

 

@logger(3)   #等價於add=logger(3)(add)

def add(x,y):

    print('===============call add================')

    time.sleep(4)

    return x+y

 

print(add(4,5))

###########################

import datetime

import time

 

def copy_properties(src):

    def wrapper(dst):

        dst.__name__ = src.__name__

        dst.__doc__ = src.__doc__

        dst.__qualname__ = src.__qualname__

        return dst

    return wrapper

 

def logger(duration):

    def _logger(fn):

        @copy_properties(fn)   #等價於add=copy_properties(add)(dst)

        def wrapper(*args,**kwargs):

            '''This is a wrapper.'''

            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(3)   #等價於add=logger(3)(add)

def add(x,y):

    print('===============call add================')

    time.sleep(2)

    return x+y

 

print(add(4,5))

##########################

def logger(fn):   #無論logger(add)是什麼都返回10,語法允許,在裝飾器角度並沒有裝飾什麼,把原函數add給廢掉了

    return 10

 

@logger   #add=logger(add)

def add(x,y):

    return x+y

 

print(add)

############################

import datetime

import time

 

def copy_properties(src):

    def wrapper(dst):

        dst.__name__ = src.__name__

        dst.__doc__ = src.__doc__

        dst.__qualname__ = src.__qualname__

        return dst

    return wrapper

 

def logger(duration,func=lambda name,duration: print('{} took {}s'.format(name,duration))):

    def _logger(fn):

        @copy_properties(fn)

        def wrapper(*args,**kwargs):

            '''This is a wrapper.'''

            start = datetime.datetime.now()

            ret = fn(*args,**kwargs)

            delta = (datetime.datetime.now() - start).total_seconds()

            if delta > duration:

                func(fn.__name__,duration)

            return ret

        return wrapper

    return _logger

 

@logger(3)

def add(x,y):

    print('===============call add================')

    time.sleep(4)

    return x+y

 

print(add(4,5))

注:

將記錄的功能提取出來,這樣就可通過外部提供的函數來靈活的控制輸出;

 

 

 

functools

functools.update_wrapper(wrapper,wrapped,assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES)

類似copy_properties功能;

wrapper包裝函數,wrapped被包裝函數;

元組WRAPPER_ASSIGNMENTS中是要被覆蓋的屬性,__module__模塊名,__name__名稱,__qualname__限定名,__doc__文檔,__annotations__參數註解;

元組WRAPEER_UPDATES中是要被更新的屬性,__dict__屬性字典,注意字典要用update方式;

增加一個__wrapped__屬性,保留着wrapped函數;

 

例:

import datetime,time,functools

 

def logger(duration,func=lambda name,duration: print('{} took {}s'.format(name,duration))):

    def _logger(fn):

        def wrapper(*args,**kwargs):

            '''This is a wrapper.'''

            start = datetime.datetime.now()

            ret = fn(*args,**kwargs)

            delta = (datetime.datetime.now() - start).total_seconds()

            if delta > duration:

                func(fn.__name__,duration)

            return ret

        functools.update_wrapper(wrapper,fn)

        return wrapper

    return _logger

 

@logger(3)

def add(x,y):

    print('===============call add================')

    time.sleep(4)

    return x+y

 

print(add(4,5),add.__name__,add.__wrapped__,add.__dict__,sep='\n')

#######################

import datetime

import time

import functools

 

def logger(duration,func=lambda name,duration: print('{} took {}s'.format(name,duration))):

    def _logger(fn):

        @functools.wraps(fn)   #經常用

        def wrapper(*args,**kwargs):

            '''This is a wrapper.'''

            start = datetime.datetime.now()

            ret = fn(*args,**kwargs)

            delta = (datetime.datetime.now() - start).total_seconds()

            if delta > duration:

                func(fn.__name__,duration)

            return ret

        return wrapper

    return _logger

 

@logger(3)

def add(x,y):

    print('===============call add================')

    time.sleep(4)

    return x+y

 

print(add(4,5),add.__name__,add.__wrapped__,add.__dict__,sep='\n')

#######################

import functools

   

def logger(fn):

    @functools.wraps(fn)

    def wrapper(*args,**kwargs):

        '''This is a wrapper'''

        print('begin')

        ret = fn(*args,**kwargs)

        print('end')

        return ret

    print('{} {}'.format(id(wrapper),id(fn)))

    return wrapper

 

@logger

def add(x,y):

    '''

    This is a additional.

   

    return int

    x int

    y int

    '''

    ret = x + y

    return ret

 

print(add.__name__,add.__doc__,add.__qualname__,sep='\n')

print('*'*50)

print(id(add.__wrapped__))

print(add(4,5))

print(add.__wrapped__(4,5))   #用包裝函數調用,如add(4,5);而不要用被包裝函數調用,如add.__wrapped__(4,5)

 

 

 

文檔字符串:

python的文檔:

在函數語句塊的第一行,且習慣是多行的文本,所以多使用三引號,約定使用雙引號;

慣例是首字母大寫,第一行寫概述,空一行,第三行寫詳細描述;

可使用特殊屬性__doc__訪問這個文檔;

 

例:

In [28]: def add(x,y):

    ...:     '''

    ...:     This is a funtion of addition.

    ...:    

    ...:     return int

    ...:     x int

    ...:     y int

    ...:     '''

    ...:     return x+y

    ...:

In [29]: print('name={}\ndoc={}'.format(add.__name__,add.__doc__))

name=add

doc=

    This is a funtion of addition.

   

    return int

    x int

    y int

   

 

In [30]: help(add)

……

 

 


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