functools usage

Reference: doc

In [1]:
import functools

lru_cache decorator

This can cache the result of a function, such that further call can be returned directly. Internally, it use a dict to store corresponding results, so all the arguments must be hashable. Such feature is especially useful in dynamicall programming context.

In [3]:
@functools.lru_cache(maxsize=200) # maxsize can be set to None, where infinite results are cached, caution!
def add1(n):
    print("caculating...")
    return n+1
In [4]:
add1(1), add1(2), add1(1), add1(3), add1(3)
caculating...
caculating...
caculating...
Out[4]:
(2, 3, 2, 4, 4)
In [5]:
add1.cache_info() # you can even check the hit rate!!
Out[5]:
CacheInfo(hits=2, misses=3, maxsize=200, currsize=3)
In [6]:
add1.cache_clear() # clean the cache
add1(1)
caculating...
Out[6]:
2

partial

make partial functions

In [14]:
def add(x,y):
    print(f"{x}+{y}")
    return x+y
add1 = functools.partial(add,2)
In [15]:
add1(1)
2+1
Out[15]:
3

reduce

The usage of reduce is evident, and no further explanation here

In [16]:
functools.reduce(add, [1,2,3,4,5])
1+2
3+3
6+4
10+5
Out[16]:
15
In [18]:
functools.reduce(add, [1,2,3,4,5], 10) # the third argument is the start point of reduce 
10+1
11+2
13+3
16+4
20+5
Out[18]:
25

singledispatch

function overload based on the type of the first argument

In [41]:
@functools.singledispatch
def show(arg, n): # the default function
    print("default")

@show.register(int)
def show_int(arg, n): # note the function name here is not the same as show
    return arg+n

@show.register(list)
def show_list(arg, n):
    return [a+n for a in arg]

show.register(float, lambda arg, n: arg+n) # the overload function can also register directly
Out[41]:
<function __main__.<lambda>>
In [42]:
show(2,3), show(2.2,1), show([1,2,3], 0)
Out[42]:
(5, 3.2, [1, 2, 3])
In [43]:
show((2,3),1)
default
In [44]:
show.dispatch(set), show.dispatch(list)
Out[44]:
(<function __main__.show>, <function __main__.show_list>)

wraps

Maybe the most used one in this module, used in definition of new decorators, such that relevant attrs of wrapper function keep the original form

In [45]:
# define a decorator without using wraps
def log(f):
    def wrapper(*args, **kws):
        print(f"call {f.__name__}")
        return f(*args, **kws)
    return wrapper
In [46]:
@log
def add(a, b):
    return a+b
In [47]:
add(1,2) ## the log decorator works
call add
Out[47]:
3
In [48]:
add.__name__ ## but the function name is not as expected, we hope the name is still add
# which make the decorator transparency
Out[48]:
'wrapper'
In [55]:
# try define the same decorator with wrap tool
def log(f):
    @functools.wraps(f)
    def wrapper(*args, **kws):
        print(f"call {f.__name__}")
        return f(*args, **kws)
    return wrapper
In [56]:
@log
def add(a, b):
    return a+b
add(1,2)
call add
Out[56]:
3
In [57]:
add.__name__ ## now the relevant attrs of add is correct again
Out[57]:
'add'