1 partial


In [5]:
import functools
def myfunc(a, b=2):
    '''Docstring for myfunc'''
    print('called myfunc with ', (a, b))

def show_details(name, f, is_partical=False):
    '''Show details of a callable function'''
    print('{}:'.format(name))
    print(' object:', f)
    print(' __name__:', end=' ')
    try:
        print(f.__name__)
    except AttributeError:
        print('(no __name__)')
    print(' __doc__', repr(f.__doc__))
    return

show_details('myfunc', myfunc)
p1 = functools.partial(myfunc, b=4)
show_detail('raw wrapper', p1)
print('Updating wrapper:')
print(' assign:', functools.WRAPPER_ASSIGNMENTS)
print(' update:', functools.WRAPPER_UPDATES)
print()
functools.update_wrapper(p1, myfunc)
show_details('updated wrapper', p1)


myfunc:
 object: <function myfunc at 0x10cc0f950>
 __name__: myfunc
 __doc__ 'Docstring for myfunc'
raw wrapper:
 object: functools.partial(<function myfunc at 0x10cc0f950>, b=4)
 __name__: (no __name__)
 __doc__ 'partial(func, *args, **keywords) - new function with partial application\n    of the given arguments and keywords.\n'
Updating wrapper:
 assign: ('__module__', '__name__', '__qualname__', '__doc__', '__annotations__')
 update: ('__dict__',)

updated wrapper:
 object: functools.partial(<function myfunc at 0x10cc0f950>, b=4)
 __name__: myfunc
 __doc__ 'Docstring for myfunc'

besides function, partial can also be used callables


In [7]:
class Myclass(object):
    def __call__(self, e, f=6):
        '''Docstring for Myclass.__call__'''
        print(' called object with', (self, e, f))

o = Myclass()
show_details('instance', o)
o('e goes here')
print()
p = functools.partial(o, e='default')
functools.update_wrapper(p, o)
show_details('instance wrapper', o)
p()


instance:
 object: <__main__.Myclass object at 0x10cb3b940>
 __name__: (no __name__)
 __doc__ None
 called object with (<__main__.Myclass object at 0x10cb3b940>, 'e goes here', 6)

instance wrapper:
 object: <__main__.Myclass object at 0x10cb3b940>
 __name__: (no __name__)
 __doc__ None
 called object with (<__main__.Myclass object at 0x10cb3b940>, 'default', 6)

2 Comparison


In [14]:
import functools
import inspect
from pprint import pprint


@functools.total_ordering
class MyObject(object):

    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        print('  testing __eq__({}, {})'.format(
            self.val, other.val))
        return self.val == other.val

    def __gt__(self, other):
        print('  testing __gt__({}, {})'.format(
            self.val, other.val))
        return self.val > other.val


print('Methods:\n')
pprint(inspect.getmembers(MyObject, inspect.isfunction))

a = MyObject(1)
b = MyObject(2)

print('\nComparisons:')
for expr in ['a < b', 'a <= b', 'a == b', 'a >= b', 'a > b']:
    print('\n{:<6}:'.format(expr))
    result = eval(expr)
    print('  result of {}: {}'.format(expr, result))


Methods:

[('__eq__', <function MyObject.__eq__ at 0x10cc447b8>),
 ('__ge__', <function _ge_from_gt at 0x10b0277b8>),
 ('__gt__', <function MyObject.__gt__ at 0x10cc44840>),
 ('__init__', <function MyObject.__init__ at 0x10cc44620>),
 ('__le__', <function _le_from_gt at 0x10b027840>),
 ('__lt__', <function _lt_from_gt at 0x10b027730>)]

Comparisons:

a < b :
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a < b: True

a <= b:
  testing __gt__(1, 2)
  result of a <= b: True

a == b:
  testing __eq__(1, 2)
  result of a == b: False

a >= b:
  testing __gt__(1, 2)
  testing __eq__(1, 2)
  result of a >= b: False

a > b :
  testing __gt__(1, 2)
  result of a > b: False

Sort() function in Python 3 supports cmp no longer.


In [15]:
import functools


class MyObject:

    def __init__(self, val):
        self.val = val

    def __str__(self):
        return 'MyObject({})'.format(self.val)


def compare_obj(a, b):
    """Old-style comparison function.
    """
    print('comparing {} and {}'.format(a, b))
    if a.val < b.val:
        return -1
    elif a.val > b.val:
        return 1
    return 0


# Make a key function using cmp_to_key()
get_key = functools.cmp_to_key(compare_obj)

def get_key_wrapper(o):
    "Wrapper function for get_key to allow for print statements."
    new_key = get_key(o)
    print('key_wrapper({}) -> {!r}'.format(o, new_key))
    return new_key


objs = [MyObject(x) for x in range(5, 0, -1)]

for o in sorted(objs, key=get_key_wrapper):
    print(o)


key_wrapper(MyObject(5)) -> <functools.KeyWrapper object at 0x10caeb590>
key_wrapper(MyObject(4)) -> <functools.KeyWrapper object at 0x10caeb7d0>
key_wrapper(MyObject(3)) -> <functools.KeyWrapper object at 0x10caebaf0>
key_wrapper(MyObject(2)) -> <functools.KeyWrapper object at 0x10caeb990>
key_wrapper(MyObject(1)) -> <functools.KeyWrapper object at 0x10caebc90>
comparing MyObject(4) and MyObject(5)
comparing MyObject(3) and MyObject(4)
comparing MyObject(2) and MyObject(3)
comparing MyObject(1) and MyObject(2)
MyObject(1)
MyObject(2)
MyObject(3)
MyObject(4)
MyObject(5)

3 Caching


In [16]:
import functools


@functools.lru_cache()
def expensive(a, b):
    print('expensive({}, {})'.format(a, b))
    return a * b


MAX = 2

print('First set of calls:')
for i in range(MAX):
    for j in range(MAX):
        expensive(i, j)
print(expensive.cache_info())

print('\nSecond set of calls:')
for i in range(MAX + 1):
    for j in range(MAX + 1):
        expensive(i, j)
print(expensive.cache_info())

print('\nClearing cache:')
expensive.cache_clear()
print(expensive.cache_info())

print('\nThird set of calls:')
for i in range(MAX):
    for j in range(MAX):
        expensive(i, j)
print(expensive.cache_info())


First set of calls:
expensive(0, 0)
expensive(0, 1)
expensive(1, 0)
expensive(1, 1)
CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)

Second set of calls:
expensive(0, 2)
expensive(1, 2)
expensive(2, 0)
expensive(2, 1)
expensive(2, 2)
CacheInfo(hits=4, misses=9, maxsize=128, currsize=9)

Clearing cache:
CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)

Third set of calls:
expensive(0, 0)
expensive(0, 1)
expensive(1, 0)
expensive(1, 1)
CacheInfo(hits=0, misses=4, maxsize=128, currsize=4)

To prevent the cache from growing without bounds in a long-running process, it is given a maximum size. The default is 128 entries, but that can be changed for each cache using the maxsize argument.

4 Reducing


In [22]:
import functools
def do_reduce(a,b):
    print('do_reduce({},{})'.format(a, b))
    return a+b

data = range(1, 5)
print(data)
result = functools.reduce(do_reduce, data)
print('result is {}'.format(result))


range(1, 5)
do_reduce(1,2)
do_reduce(3,3)
do_reduce(6,4)
result is 10

with initializes


In [23]:
import functools


def do_reduce(a, b):
    print('do_reduce({}, {})'.format(a, b))
    return a + b


data = range(1, 5)
print(data)
result = functools.reduce(do_reduce, data, 99)
print('result: {}'.format(result))


range(1, 5)
do_reduce(99, 1)
do_reduce(100, 2)
do_reduce(102, 3)
do_reduce(105, 4)
result: 109

5 Generic Method


In [24]:
import functools


@functools.singledispatch
def myfunc(arg):
    print('default myfunc({!r})'.format(arg))


@myfunc.register(int)
def myfunc_int(arg):
    print('myfunc_int({})'.format(arg))


@myfunc.register(list)
def myfunc_list(arg):
    print('myfunc_list()')
    for item in arg:
        print('  {}'.format(item))


myfunc('string argument')
myfunc(1)
myfunc(2.3)
myfunc(['a', 'b', 'c'])


default myfunc('string argument')
myfunc_int(1)
default myfunc(2.3)
myfunc_list()
  a
  b
  c