functools

 

 

 

注:

from collections import namedtuple

from collections import defaultdict

from collections import OrderedDict

pycharmctrl+选中方法,可查看源码;

 

 

 

functools模块:

from functools import update_wrapper

from functools import wraps   #装饰器,用于复制被包装函数属性@wraps(fn)

from functools import partial

from functools import lru_cache

from functools import total_ordering

 

 

 

partial方法:

偏函数,把函数部分的参数固定下来(闭包,把源函数及参数保存下来),相当于为部分的参数添加了一个固定的默认值,形成一个新的函数并返回;

partial生成的新函数,是对源函数的封装;

很有用,在使用函数封装等技巧时用到,functools.wraps里就用到了partial

 

例:

In [1]: import functools

In [2]: def add(x,y)->int:

   ...:     return x+y

   ...:

In [3]: newadd=functools.partial(add)

In [4]: newadd(4,5)

Out[4]: 9

In [5]: newadd=functools.partial(add,y=4)

In [6]: newadd(4)

Out[6]: 8

In [7]: newadd=functools.partial(add,x=4)

In [8]: newadd(y=5)

Out[8]: 9

In [9]: newadd=functools.partial(add,6)

In [10]: newadd(5)   #相当于functools.partial(add,6)(5),一般不这么写,分开写

Out[10]: 11

In [11]: import inspect

In [12]: inspect.signature(add)

Out[12]: <Signature (x, y) -> int>

In [13]: inspect.signature(newadd)

Out[13]: <Signature (y) -> int>

 

例:

In [14]: def add(x,y,*args)->int:

    ...:     print(args,'\t\t',x+y)

    ...:     return x+y

    ...:

In [15]: newadd=functools.partial(add,4,5,6,7)

In [16]: newadd(8)

(6, 7, 8)             9

Out[16]: 9

In [17]: newadd(8,9)

(6, 7, 8, 9)                  9

Out[17]: 9

In [18]: newadd(8,9,y=20,x=26)   #新函数中没有关键字参数

---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-18-618583db3e76> in <module>()

----> 1 newadd(8,9,y=20,x=26)

TypeError: add() got multiple values for argument 'x'

In [19]: newadd()

(6, 7)                  9

Out[19]: 9

In [20]: inspect.signature(add)

Out[20]: <Signature (x, y, *args) -> int>

In [21]: inspect.signature(newadd)

Out[21]: <Signature (*args) -> int>

 

 

 

functools.lru_cache

import functools

functools.lru_cache(maxsize=128,typed=False),带参装饰器,使用时必须加(),一般不加typed=Truemaxsize=None

lruleast-recently-used,最近最少使用;

如果maxsize设为None,则禁用lru功能,且缓存可无限制增长;当maxsize2的幂时,lru功能执行的最好;

如果typed设为True,则不同类型的函数参数将单独缓存,如f(3)f(3.0)将被视为具有不同结果的不同调用;

缓存机制是什么?dict数据类型;

 

lru_cache装饰器应用:

使用前提:同样的函数参数一定得到同样的结果;函数执行时间很长,且要多次执行;

本质,函数调用的参数=>返回值;

缺点,不支持缓存过期,key无法过期、失效;不支持清除操作;不支持分布式,是一个单机的缓存(单机内共用;单机内进程用;单机内线程用);

适用场景,单机上需要空间换时间的地方,可用缓存来使计算变为快速的查询

 

注:

好的算法借用缓存,是锦上添花;

如果算法不优秀,借用缓存相当于是空间换时间;时间换空间(压缩);

buffer,缓冲,FIFO先进先出队列;

 

例:

import functools

import time

 

@functools.lru_cache()   #带参装饰器必须加()

def add(x,y=5):

    time.sleep(3)

    ret = x + y

    print(ret)

    return ret

 

In [24]: add(4)

9

Out[24]: 9

In [25]: add(4)   #

Out[25]: 9

In [26]: add(4,5)

9

Out[26]: 9

In [27]: add(4,5)   #

Out[27]: 9

In [28]: add(4,y=5)

9

Out[28]: 9

In [29]: add(4,y=5)   #

Out[29]: 9

In [30]: add(x=4,y=5)

9

Out[30]: 9

In [31]: add(y=5,x=4)   #关键字参数相同,对收集的关键字参数进行排序(将字典内的kv对转为二元组)

Out[31]: 9

In [32]: add(4.0,5)   #快,typed=False

Out[32]: 9

 

例:

import functools

 

@functools.lru_cache()

def fib(n):

    if n < 2:

        return n

    return fib(n-1) + fib(n-2)

 

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

 

习题:

1、实现一个cache装饰器,实现过期,可自动清除,可以不换出;

2、写一个命令分发器:

程序员可方便的注册函数到某一个命令,用户输入命令时,路由到注册的函数;

如果此命令没有对应的注册函数,执行默认函数;

用户输入用input('>> ')

 

1

简化设计,函数的形参定义不包含可变位置参数、可变关键字参数和keyword-only参数,可不考虑缓存满了之后的换出问题;

 

数据类型的选择:

缓存的应用场景,是有数据需要频繁查询,且每次查询都需要大量计算或等待时间之后才能返回结果的情况,使用缓存来提高查询速度,用空间换取时间;

 

cache选用什么数据结构?

便于查询的,且能快速找到数据的数据结构;

每次查询的时候,只要输入一致,就该得到同样的结果,顺序一致,如减法函数,参数顺序不一致,结果不一样;

基于以上分析,此数据结构是dict,通过一个key对应一个value

key是参数列表组成的结构,value是函数的返回值,难点在于key如何处理;

 

key的存储?

key必须是hash类;

key能接受到位置参数和关键字参数传参;

位置参数是被收集在一个tuple中的,本身就有顺序;

关键字参数被收集在一个字典中,本身无序,这会带来一个问题,传参的顺序未必是字典中保存的数据,如何解决?1OrderedDict2、不用OrderedDict,用一个tuple保存排过序的dictitemkv对;

 

key的异同?

什么才算是相同的key

lru_cache实现了,add(y=5,x=4)add(x=4,y=5)相同,而add(4,5)add(4,y=5)add(x=4,y=5)均不同,可看出单独处理了位置参数和关键字参数;但是函数定义中有默认值def add(4,y=5),如何理解add(4)add(4,5)是否一样?若认为一样,lru_cache无能为力,需要使用inspect来自己实现算法;

 

key的要求?

必须是hashable

由于key是所有实参组合而成,而且最好要作为key的,key一定要可hashable,但如果key有不可hashable类型数据,就无法完成,如def add(x=[],y=5)这种情况不考虑;

缓存必须使用key,但key必须可hashable,所以只能使用实参是不可变类型的函数调用;

 

key算法设计:

inspect模块获取函数签名后,取parameters,这是一个有序字典,会保存所有参数的信息;

构建一个字典params_dict,按位置顺序从args中依次对应参数名和传入的实参,组成kv对,存入params_dict中,kwargs所有值updateparams_dict中;

如果使用了缺省值的参数,不会出现在实参params_dict中,会出现在签名的parameters中;

 

调用的方式:

普通函数调用可以,但过于明显;

最好类似lru_cache这种方式,让调用者无察觉的使用缓存,构建装饰器函数

 

过期什么?

某一个key过期,可以对每一个key单独设置过期时间,也可以对这些key统一设定过期时间,此例统一设定过期时间,当key生存超过了这个时间,就自动被清除;

此例未考虑多线程问题,这种过期机制,每一次都有遍历所有数据,大量数据时,遍历会有效率问题;

 

清除的时机?

用到某个key之前,先判断是否过期,如果过期重新调用函数生成新的key对应value值;

一个线程负责清除过期的key(之后实现),本例在创建key之前,清除所有过期的key

 

value的设计?

key=>(v,createtimestamp),适合key过期的时间都是统一设定,配合用这种;

key=>(v,createtimestamp,duration),每一个key就可单独控制过期时间,这种设计,-1可表示永不过期,正整数表示持续一段时间过期;

 

例:

from functools import wraps

import inspect

import time

 

def m_cache(fn):

    local_cache = {}

    @wraps(fn)

    def wrapper(*args ,**kwargs):

        #print(args,kwargs)

        key_dict = {}

        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

 

#         for k,v in kwargs.items():

#             key_dict[k] = v

        key_dict.update(kwargs)   #关键字参数

       

        key = tuple(sorted(key_dict.items()))

        if key not in local_cache.keys():

            ret = fn(*args,**kwargs)

            local_cache[key] = ret

        return local_cache[key]

    return wrapper

 

@m_cache

def add(x,y=5):

    time.sleep(3)

    ret = x + y

    return ret

注:

解决了

add(4,5)

add(4,y=5)

add(x=4,y=5)

add(y=5,x=4)

 

例:

from functools import wraps

import inspect

import time

 

def m_cache(fn):

    local_cache = {}

    @wraps(fn)

    def wrapper(*args ,**kwargs):

#        print(args,kwargs)

        key_dict = {}

        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

 

#         for k,v in kwargs.items():

#             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

               

        key = tuple(sorted(key_dict.items()))

        if key not in local_cache.keys():

            ret = fn(*args,**kwargs)

            local_cache[key] = ret

        return local_cache[key]

    return wrapper

 

@m_cache

def add(x,y=5):

    time.sleep(3)

    ret = x + y

    return ret

注:

解决了

add(4)

 

例:

from functools import wraps

import inspect

import time

import datetime

 

def m_cache(duration):

    def _cache(fn):

        local_cache = {}

        @wraps(fn)

        def wrapper(*args ,**kwargs):

    #         print(args,kwargs)

   

            expire_keys = []   #此处要将函数抽到一个函数里,用单独线程来运行

            for k,(_,ts) in local_cache.items():

                if datetime.datetime.now().timestamp() - ts > duration:

                    expire_keys.append(k)

            for k in expire_keys:

                local_cache.pop(k)

 

            key_dict = {}

            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

 

    #         for k,v in kwargs.items():

    #             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

 

            key = tuple(sorted(key_dict.items()))

            if key not in local_cache.keys():

                ret = fn(*args,**kwargs)

    #             local_cache[key] = ret

                local_cache[key] = (ret,datetime.datetime.now().timestamp())

            return local_cache[key]

        return wrapper

    return _cache

 

def logger(fn):

    @wraps(fn)

    def wrapper(*args,**kwargs):

        start = datetime.datetime.now()

        ret = fn(*args,**kwargs)

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

        print(delta)

        return ret

    return wrapper

 

@m_cache(5)   #两装饰器不分先后

@logger

def add(x,y=5):

    time.sleep(3)

    ret = x + y

    return ret

注:

解决了缓存过期;

 

例:

from functools import wraps

import inspect

import time

import datetime

 

def m_cache(duration):

    def _cache(fn):

        local_cache = {}

        @wraps(fn)

        def wrapper(*args ,**kwargs):

    #         print(args,kwargs)

           

            def clear_expire(cache):

                expire_keys = []

                for k,(_,ts) in local_cache.items():

                    if datetime.datetime.now().timestamp() - ts > duration:

                        expire_keys.append(k)

                for k in expire_keys:

                    local_cache.pop(k)

            clear_expire(local_cache)

           

            def make_key():

                key_dict = {}

                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

 

        #         for k,v in kwargs.items():

        #             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

                local_cache[key] = (ret,datetime.datetime.now().timestamp())

            return local_cache[key]

        return wrapper

    return _cache

 

def logger(fn):

    @wraps(fn)

    def wrapper(*args,**kwargs):

        start = datetime.datetime.now()

        ret = fn(*args,**kwargs)

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

        print(delta)

        return ret

    return wrapper

 

@m_cache(5)

@logger

def add(x,y=5):

    time.sleep(3)

    ret = x + y

    return ret

注:

将各功能模块化(抽取为函数);

 

2

例:

commands = {}

 

def reg(name,fn):

    commands[name] = fn

   

def default_func():

    print('unknown command')

   

def dispatcher():

    while True:

        cmd = input('>> ')

        if cmd.strip() == 'quit':

            return

        commands.get(cmd,default_func)()

       

def foo1():

    print('welcome magedu')

   

def foo2():

    print('welcome python')

   

reg('mag',foo1)

reg('py',foo2)

 

dispatcher()

 

例:

commands = {}

 

def reg(name):

    def _reg(fn):

        commands[name] = fn

        return fn

    return _reg

   

def default_func():

    print('unknown command')

   

def dispatcher():

    while True:

        cmd = input('>> ')

        if cmd.strip() == 'quit':

            return

        commands.get(cmd,default_func)()

 

@reg('mag')       

def foo1():

    print('welcome magedu')

   

@reg('py')

def foo2():

    print('welcome python')

   

# reg('mag',foo1)

# reg('py',foo2)

 

dispatcher()

注:

封装;

 

例:

def cmds_dispatcher():

    commands = {}

 

    def reg(name):

        def _reg(fn):

            commands[name] = fn

            return fn

        return _reg

   

    def default_func():

        print('unknown command')

 

    def dispatcher():

        while True:

            cmd = input('>> ')

            if cmd.strip() == 'quit':

                return

            commands.get(cmd,default_func)()

           

    return reg,dispatcher

 

reg,dispatcher = cmds_dispatcher()

 

@reg('mag')       

def foo1():

    print('welcome magedu')

   

@reg('py')

def foo2():

    print('welcome python')

   

# reg('mag',foo1)

# reg('py',foo2)

 

dispatcher()

注:

再次封装;

 

 

 

functools.total_ordering

 

__lt____le____eq____gt____ge__,是比较大小必须实现的方法,但全部写完太麻烦,使用此装饰器可大大简化代码;

查看源码,该函数实现复杂,效率不高;

此装饰器要求,类中必须实现__eq__(),另在其它比较方法中选一个即可;

 

如,定义了__eq__()__lt__(),在用其它方法时,该装饰器才推演(大量计算),在用到==<时则直接使用,不进行推演;

 

在比较时,若不用此装饰器,则要把所有的比较方法写全,如写不全则报错TypeError: '<' not supported between instances of 'A' and 'A',例如只写了__ge__()方法,在使用>=<=时没问题,但当使用<>时报错;

 

functools.total_ordering应用场景:

偶尔比较时;

若经常比较,要把比较方法全部写到类里,这样效率高;

 

例:

from functools import total_ordering

 

@total_ordering

class A:

    def __init__(self,x):

        self.x = x

 

    # def __ge__(self, other):

    #     return self.x >= other.x

 

    def __eq__(self, other):

        return self.x == other.x

 

    def __lt__(self, other):

        return self.x <= other.x

 

a = A(5)

b = A(6)

print(a >= b)

print(a <= b)

print(a < b)

print(a > b)

输出:

False

True

True

False