1. 程式人生 > >Python第五周 學習筆記(1)

Python第五周 學習筆記(1)

學記筆記

高階函數
  • First Class Object
  • 函數也是對象,可調用的對象
  • 函數可以作為普通變量、參數、返回值等等

  • 數學概念 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方式,簡化調用

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

文檔字符串

  • 在函數語句塊的第一行,且習慣是多行的文本,所以多使用三引號
  • 慣例是首字母大寫,第一行寫概述,空一行,第三行寫詳細描述
  • 可以使用特殊屬性doc訪問這個文檔

裝飾器副作用

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

解決方法:

1.提供一個函數,被封裝函數屬性 ==copy==> 包裝函數屬性

def copy_properties(src, dst): # 可以改造成裝飾器
    dst.__name__ = src.__name__
    dst.__doc__ = src.__doc__
  • 通過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無法過期、失效
    • 不支持清除操作
    • 不支持分布式,是一個單機的緩存
    • 適用場景,單機上需要空間換時間的地方,可以用緩存來將計算變成快速的查詢

Python第五周 學習筆記(1)