偏函數 partial、lru_cache、自編cache函數、命令分發器

把函數部分參數固定下來,相當於爲部分的參數添加了一個固定的默認值,

形成一個新的函數並返回

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

import functools
import inspect


def add(x, y, *args) -> int:
    print(args)
    return x + y


newadd = functools.partial(add, 1, 3, 6, 5)
print("a", newadd(7))
print(newadd(7, 10))
# print(newadd(9, 10, y=20, x=26))  1,3已經位置參數對應給了x,y,而後面又關鍵字傳x,y
print(newadd())
print(inspect.signature(add))
print(inspect.signature(newadd))

out:
(6, 5, 7)
a 4
(6, 5, 7, 10)
4
(6, 5)
4
(x, y, *args) -> int
(*args) -> int

 核心源碼:

def partial(func, *args, **keywords):
    def newfunc(*fargs, **fkeywords):
        newkeywords = keywords.copy()
        newkeywords.update(fkeywords)
        return func(*(args + fargs), **newkeywords)  # 如果是newadd = functools.partial(add, 1),newadd(3) (1,)+(3,)=(1,3)
    newfunc.func = func
    newfunc.args = args
    newfunc.keywords = keywords
    return newfunc

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

Least-recently-used裝飾器,lru,最近最少使用。

如果maxsize設置爲NONE,則禁用lru功能,並且緩存可以無限增長

當maxsize爲二的冪時,lru功能執行最好

如果typed設置爲True,則不同類型的函數參數將單獨緩存。

例如:f(3)和f(3.0)將被視爲具有不同結果的調用

#lru_cache 核心源碼


def _make_key(args, kwds, typed,
             kwd_mark = (object(),),
             fasttypes = {int, str, frozenset, type(None)},
             sorted=sorted, tuple=tuple, type=type, len=len):
    key = args
    if kwds:
        sorted_items = sorted(kwds.items())
        key += kwd_mark
        for item in sorted_items:
            key += item
    if typed:
        key += tuple(type(v) for v in args)
        if kwds:
            key += tuple(type(v) for k, v in sorted_items)
    elif len(key) == 1 and type(key[0]) in fasttypes:
        return key[0]
    return _HashedSeq(key)



class _HashedSeq(list):

    __slots__ = 'hashvalue'

    def __init__(self, tup, hash=hash):
        self[:] = tup               # lst = []   lst[:] = tup
        self.hashvalue = hash(tup)

    def __hash__(self):
        return self.hashvalue

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

Key:

functools._make_key((4,6),{'z':3},False)
functools._make_key((4,6,3),{},False)
functools._make_key(tuple(),{'z':3,'x':4,'y':6},False)
functools._make_key(tuple(),{'z':3,'x':4,'y':6}, True)
#菲波拉契數列改造

import functools

@functools.lru_cache() # maxsize=None
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)


print([fib(x)for x in range(35)])

out:
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887]

使用前提:

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

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

本質是函數調用的參數--返回值

缺點:

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

不支持清除操作

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

 

適用場景:

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

 

練習:

1、實現一個cache裝飾器,實現可過期,可刪除功能,可以不換出

2、寫一個命令分發器

程序員可以方便的註冊函數到某一個命令,用戶輸出命令時,路由到註冊的函數

如果此命令沒有對應的註冊函數,執行默認函數

用戶輸入用input(">>")

 

第一題:

from functools import wraps
import inspect
import time
import datetime


def m_cache(duration):  # 第一步,傳入5
    def _cache(fn):  # 第三步,傳入add
        local_cache = {}

        @wraps(fn)  # add
        def wrapper(*args, **kwargs):
            # local_cache 有沒有過期的key
            def clear_expire(cache):
                expire_keys = []
                for k, (_, timestamp) in cache.items():
                    if datetime.datetime.now().timestamp() - timestamp > duration:  # 5
                        expire_keys.append(k)
                for ekey in expire_keys:
                    cache.pop(ekey)
                print(expire_keys)

            clear_expire(local_cache)

            def make_key():
                key_dict = {}  # sorted
                sig = inspect.signature(fn)
                params = sig.parameters  # 有序字典
                param_list = list(params.keys())
                # 位置參數
                for i, v in enumerate(args):
                    k = param_list[i]
                    key_dict[k] = v
                # 關鍵字參數
                key_dict.update(kwargs)  # 直接合並
                # 缺省值處理
                for k in params.keys():
                    if k not in key_dict.keys():
                        key_dict[k] = params[k].default

                return tuple(sorted(key_dict.items()))

            key = make_key()

            if key not in local_cache.keys():
                ret = fn(*args, **kwargs)
                local_cache[key] = (ret, datetime.datetime.now().timestamp())  # local_cache (k,(tuple))
            return key, local_cache[key]  # 第九步,此wrapper執行完成,返回值。
        return wrapper  # 第四步,返回wrapper
    return _cache  # 第二步,返回_cache函數


def logger(fn):  # 第五步,傳入wrapper(第四步裏的wrapper)
    @wraps(fn)  # 第四步裏的wrapper
    def wrapper(*args, **kwargs):    # 第七步,當60行真正調用時,爲此wrapper(5, 8)
        start = datetime.datetime.now()
        ret = fn(*args, **kwargs)  # 第八步,執行第四步裏的wrapper(5, 8)
        delta = (datetime.datetime.now() - start).total_seconds()  # 第十步,計算時間
        print(delta)
        return ret  # 第十一步,返回ret的值
    return wrapper  # 第六步,最後返回wrapper     當72行真正調用時,爲此wrapper(5, 8)


@logger    # logger(m_cache(add))
@m_cache(5)  # m_cache()(add) 相當於_cache(add)      # add() = logger(_cache(add))()
def add(x, y=5):
    time.sleep(5)
    ret = x + y
    return ret


print(add(5, 8))  # add(5,8) = logger(_cache(add))(5, 8)
print(add(x=5, y=8))

time.sleep(3)

print(add(5, 8))
print(add(x=5, y=8))

out:
[]
5.006635
((('x', 5), ('y', 8)), (13, 1550406262.473862))
[]
0.00014
((('x', 5), ('y', 8)), (13, 1550406262.473862))
[]
0.00023
((('x', 5), ('y', 8)), (13, 1550406262.473862))
[]
0.000131
((('x', 5), ('y', 8)), (13, 1550406262.473862))  #由於 每次都是重新定義,所以沒有過期的key
print(add(x=5, y=8))

out:

[(('x', 5), ('y', 8))]  # 等會單獨執行,就是有過期的緩存被清除了
5.008303
((('x', 5), ('y', 8)), (13, 1550406285.663602))

 第二題:

# 命令分發器


def cmds_dispatcher():
    commands = {}

    def reg(name):  # 註冊
        def _reg(fn):
            commands[name] = fn
            return fn
        return _reg

    def defaultfun():
        print('Unknown command')

    def dispatcher():
        while True:
            cmd = input('>>')
            if cmd.strip() == 'quit':
                return
            commands.get(cmd, defaultfun)()

    return reg, dispatcher


reg, dispatcher = cmds_dispatcher()
# r, d = cmds_dispatcher()


@reg('ronn')
def foo1():  # 自定義函數
    print('welcome to foo1')


@reg('tonn')
def foo2():
    print('welcome to foo2')


dispatcher()


out:
>>hello
Unknown command
>>ronn
welcome to foo1
>>tonn
welcome to foo2
>>quit

 

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