小結

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

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

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

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

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

  • 輸出一個函數

內建高階函數

  • sorted(iterable[, key][, reverse])

  • 排序

  • filter(function, iterable) --> filter object

  • 過濾數據

  • map(func, *iterables) --> map object

  • 映射

sorted(iterable[, key][, reverse]) 排序

  • 返回一個新的列表,對一個可迭代對象的所有元素排序,排序規則爲key定義的函數,reverse表示是否排序翻轉

filter(function, iterable)

  • 過濾可迭代對象的元素,返回一個迭代器

  • function一個具有一個參數的函數,返回bool

map(function, *iterables) --> map object

  • 對多個可迭代對象的元素按照指定的函數進行映射,返回一個迭代器

柯里化Currying

  • 指的是將原來接受兩個參數的函數變成新的接受一個參數的函數的過程。新的函數返回一個以原有第二個參數爲參數的函數

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)


通過嵌套函數就可以把函數轉換成柯里化函數

裝飾器

在OOP設計模式中,裝飾器屬於一種裝飾模式

裝飾器語法是一個語法糖

def logger(func):
    def wrapper(*args, **kwargs):
        print('call ' + func.__name__)
        return func(*args, **kwargs)
    return wrapper


@logger # 等價於add = logger(add)
def add(x,y):
return x + y


裝飾器(無參)

  • 它是一個函數

  • 函數作爲它的形參

  • 返回值也是一個函數

  • 可以使用@functionname方式,簡化調用

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

列入一個加法函數
def  add (x,y)
return x,y

這個函數只是簡單定義函數,但是如果想要函數增加打印功能

def add (x,y):
return  x + y
def logger (fn):
print (" call function {} .x={} y={}".format(fn.__name__ ,4 ,5))
ret = fn(3,5)
return ret
print ('result = {}'.format(logger(add)))


文檔字符串

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

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

  • 可以使用特殊屬性doc訪問這個文檔

裝飾器副作用

  • 原函數對象的屬性都被替換了(執行後返回的是wrapper的屬性),而使用裝飾器,我們的需求是查看被封裝函數的屬性

解決方法:

1.提供一個函數,被封裝函數屬性 ==copy==> 包裝函數屬性
def copy_properties(src, dst): # 可以改造成裝飾器
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__

如果除了加法還想增加減法到話

可以寫爲

def add (x,y):
return  x + y
def sub (x,y):
return x - y
def logger (fn,x,y):
print (" call function {} .x={} y={}".format(fn.__name__ ,x,y))
ret = fn(x,y)
return ret
print ('result = {}'.format(logger(add,3,2)))
print ('result = {}'.format(logger(sub,4,5))

)

而且增加了可以直接調用函數

如果想要變得更加同的話

目前傳參只有倆種

關鍵字傳參**kwargs

位置傳參 *args

第一個無參裝飾器

def logger (fn):
def wrapper (*args,**kwargs):
print (" call function {} .x={} y={}".format(fn.__name__ ,*args,**kwargs))
ret = fn(*args,**kwargs)
return ret
return inner
@logger   # =>add = logger (add)
def add (x,y):
return  x + y
#add = wrapper (add)
ret = add (4,5)
print (ret)

如果函數在增加功能函數的話

可以如下

加一些計算時間功能

函數所消耗時間(注意要先定義一個函數)

import datetime
import time
def logger (fn):
def wrapper (*args,**kwargs):
print ("前面增強")
start = datetime.datetime.now ()
#print (" call function {} .x={} y={}".format(fn.__name__ ,*args,**kwargs))
ret = fn(*args,**kwargs)
delta = (datetime.datetime.now()-start).total_seconds()
print ('Function {} took {}s'.format(fn.__name__,delta))
if delta >5:
print ('so slow')
else:
print ('so fast')
print ("後面增強")
return ret
return wrapper
@logger   # =>add = logger (add)
def add (x,y):
time.sleep (3)
return  x + y
ret = add (4,5)
print (ret)

文檔字符串

要求放在函數內部的第一行

文檔字符串使用慣例:它的首行簡述函數功能,第二行空行,第三行爲函數的具體描述。

帶參裝飾器

首先柯里化

import datetime 
import time 
def copy_porperties (src):
    def _copy(dest):
        dest.__name__ = src.__name__
        dest.__doc__ = src.__doc__
        return dest
    return _copy
   # return src
def logger (fn):
    @copy_porperties(fn)#wrapper = copy_porperties(wrapper)是下面的wrapper
                                =  _copy(wrapper)
#     def copy_porperties (src,dest):
#         dest.__name__ = src.__name__
#         dest.__doc__ = src.__doc__
    def wrapper (*args,**kwargs):
        """The Function is wrapper"""
        print ("前面增強")
        start = datetime.datetime.now ()
        #print (" call function {} .x={} y={}".format(fn.__name__ ,*args,**kwargs))
        ret = fn(*args,**kwargs)
        delta = (datetime.datetime.now()-start).total_seconds()
        print ('Function {} took {}s'.format(fn.__name__,delta))
        if delta >5:
            print ('so slow')
        else:
            print ('so fast')
        print ("後面增強")
        return ret
    #copy_porperties(fn)(wrapper)
    return wrapper
@logger   # =>add = logger (add)
# w = logger (add)
# copy_porperties(add,wrapper)
def add (x,y):
    """The Function is add """
    time.sleep(0)
    return  x + y
ret = add (4,5)
print (ret)
print(add.__name__)
print (add.__doc__)
#print ("0-------------------")
#help (add)


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

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

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

2.使用functools.update_wrapper(wrapper, 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):
            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 functools.update_wrapper(wrapper, fn)
    return _logger

@logger(5) # add = logger(5)(add)
def add(x,y):
    time.sleep(1)
    return x + y
print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')


3.使用@functools.wraps(wrapped)

import datetime, time, functools

def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))):
    def _logger(fn):
        @functools.wraps(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)
            return ret
        return wrapper
    return _logger

@logger(5) # add = logger(5)(add)
def add(x,y):
    time.sleep(1)
    return x + y

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


帶參裝飾器

  • 它是一個函數 函數作爲它的形參

  • 返回值是一個不帶參的裝飾器函數

  • 使用@functionname(參數列表)方式調用

  • 可以看做在裝飾器外層又加了一層函數

將記錄的功能提取出來,這樣就可以通過外部提供的函數來靈活的控制輸出
參數註解
形如:


def add(x:int , y:int) -> int :
'''
:param x: int
:param y: int
:return: int
'''
return x + y


  • Python 3.5引入

  • 對函數的參數進行類型註解

  • 對函數的返回值進行類型註解

  • 只對函數參數做一個輔助的說明,並不對函數參數進行類型檢查

  • 提供給第三方工具,做代碼分析,發現隱藏的bug

  • 函數註解的信息,保存在annotations屬性中

  • 鍵是參數名,值是class類型

變量註解使用較少
inspect模塊
signature(callable),返回一個Signature類對象。獲取簽名(函數簽名包含了一個函數的信息,包括函數名、它的參數類型、它所在的類和名稱空間及其他信息)
sig = signature(fn)
params = sig.parameters


Signature類對象的parameters方法返回的是一個orderedDict,其key值爲fn的形參名,value值爲Parameter類對象
Parameter對象
保存在元組中,是隻讀的


  • name,參數的名字

  • annotation,參數的註解,可能沒有定義

  • default,參數的缺省值,可能沒有定義

  • empty,特殊的類,用來標記default屬性或者註釋annotation屬性的空值

  • kind,實參如何綁定到形參,就是形參的類型

  • POSITIONAL_ONLY,值必須是位置參數提供 #Python沒有實現次形參類型

  • POSITIONAL_OR_KEYWORD,值可以作爲關鍵字或者位置參數提供

  • VAR_POSITIONAL,可變位置參數,對應*args

  • KEYWORD_ONLY,keyword-only參數,對應或者args之後的出現的非可變關鍵字參數

  • VAR_KEYWORD,可變關鍵字參數,對應**kwargs

業務應用

  • 函數參數類型檢查

  • 思路

  • 函數參數的檢查,一定是在函數外

  • 函數應該作爲參數,傳入到檢查函數中

  • 檢查函數拿到函數傳入的實際參數,與形參聲明對比

  • annotations屬性是一個字典,其中包括返回值類型的聲明。假設要做位置參數的判斷,無法和字典中的聲明對應。使用inspect模塊

  • inspet模塊提供獲取對象信息的函數,可以檢查函數和類、類型檢查

  • 代碼實現:

import inspect
def check(fn):
    def wrapper(*args,**kwargs):
        sig = inspect.signature(fn)
        params = sig.parameters #是orderedDict
        values = list(params.values()) #可迭代對象轉化成列表,處理後也是有序的
        for k,v in enumerate(args): #因爲values、args都有序
            if values[k].annotation is not inspect._empty and not isinstance(v,values[k].annotation):
                return '{}`s input is {} type , expected {}'.format(v,type(v),values[k].annotation)
        for k,v in kwargs.items(): #kwargs與params都爲字典形式,鍵值相同
            if params[k].annotation is not inspect._empty and not isinstance(v, params[k].annotation):
                return '{}`s input is {} type , expected {}'.format(k,type(v),params[k].annotation)
        return fn(*args,**kwargs)
    return wrapper

@check
def add(x:int,y:int):
    return x + y


functools模塊

偏函數partial

  • 把函數部分的參數固定下來,相當於爲部分的參數添加了一個固定的默認值,形成一個新的函數並返回

  • 從partial生成的新函數,是對原函數的封裝

partial源代碼邏輯

def partial(func, *args, **keywords):
def newfunc(*fargs, **fkeywords): # 包裝函數
newkeywords = keywords.copy() #將原函數關鍵字參數拷貝後,
newkeywords.update(fkeywords)  #將調用新函數時的關鍵字參數更新
return func(*(args + fargs), **newkeywords) #調用新函數時的參數是建立偏函數時給的參
                                                                         #數,再各加上調用新函數時的參
newfunc.func = func # 保留原函數,注意:如果原函數已有裝飾器,func屬性內容有可能不是原函數名,而是裝飾器函數名
newfunc.args = args # 保留原函數的位置參數
newfunc.keywords = keywords # 保留原函數的關鍵字參數參數
return newfunc


  • partial()返回一個新函數newfunc,而newfunc返回原函數,其參數是是建立偏函數時給的參數,再各加上調用新函數時的參

LRU緩存裝飾器

  • @functools.lru_cache(maxsize=128, typed=False)

  • Least-recently-used裝飾器。lru,最近最少使用。cache緩存

  • 如果maxsize設置爲None,則禁用LRU功能,並且緩存可以無限制增長。當maxsize是二的冪時,LRU功能執行得最好

  • 如果typed設置爲True,則不同類型的函數參數將單獨緩存。例如,f(3)和f(3.0)將被視爲具有不同結果的不同調用

  • 通過一個字典緩存被裝飾函數的調用和返回值

  • 如果全爲位置實參且位置與數值都相同,則視爲相同實參,或者全傳關鍵字參數則即使順序不同也視爲相同實參,即緩存能使用上;其他情況則皆視爲不同實參

  • lru_cache使用_make_key生成參數緩存

  • 源碼:

def _make_key(args, kwds, typed,
         kwd_mark = (object(),),
         fasttypes = {int, str, frozenset, type(None)},
         tuple=tuple, type=type, len=len):
"""Make a cache key from optionally typed positional and keyword arguments

The key is constructed in a way that is flat as possible rather than
as a nested structure that would take more memory.

If there is only a single argument and its data type is known to cache
its hash value, then that argument is returned without a wrapper.  This
saves space and improves lookup speed.

"""
key = args
if kwds:
    key += kwd_mark
    for item in kwds.items():
        key += item
if typed:
    key += tuple(type(v) for v in args)
    if kwds:
        key += tuple(type(v) for v in kwds.values())
elif len(key) == 1 and type(key[0]) in fasttypes:
    return key[0]
return _HashedSeq(key)


lru_cache裝飾器應用

  • 使用前提

  • 同樣的函數參數一定得到同樣的結果

  • 函數執行時間很長,且要多次執行

  • 本質是函數調用的參數=>返回值

  • 缺點

  • 不支持緩存過期,key無法過期、失效

  • 不支持清除操作

  • 不支持分佈式,是一個單機的緩存

  • 適用場景,單機上需要空間換時間的地方,可以用緩存來將計算變成快速的查詢


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