偏函数 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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值