8.20. Functional Stdlib Functools¶
import functools
functools.partial()
functools.partialmethod()
functools.reduce()
functools.singledispatch()
functools.singledispatchmethod()
>>> import functools
>>>
>>> [method for method in dir(functools)
... if not method.startswith('_')
... and callable(getattr(functools, method))]
['GenericAlias', 'RLock', 'cache', 'cached_property', 'cmp_to_key',
'get_cache_token', 'lru_cache', 'namedtuple', 'partial', 'partialmethod',
'recursive_repr', 'reduce', 'singledispatch', 'singledispatchmethod',
'total_ordering', 'update_wrapper', 'wraps']
8.20.1. Partial¶
Create alias function and its arguments
Useful when you need to pass function with arguments to for example
map
orfilter
>>> from functools import partial
>>>
>>>
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
8.20.2. Partialmethod¶
>>> from functools import partialmethod
>>>
>>>
>>> class Cell(object):
... def __init__(self):
... self._alive = False
...
... @property
... def alive(self):
... return self._alive
...
... def set_state(self, state):
... self._alive = bool(state)
...
... set_alive = partialmethod(set_state, True)
... set_dead = partialmethod(set_state, False)
>>>
>>>
>>> c = Cell()
>>>
>>> c.alive
False
>>>
>>> c.set_alive()
>>> c.alive
True
8.20.3. Reduce¶
split-apply-combine strategy
Apply function of two arguments cumulatively to the items of iterable, from
left to right, so as to reduce the iterable to a single value. For example,
reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])
calculates
((((1+2)+3)+4)+5)
. The left argument, x, is the accumulated value and
the right argument, y, is the update value from the iterable. If the
optional initializer is present, it is placed before the items of the
iterable in the calculation, and serves as a default when the iterable is
empty. If initializer is not given and iterable contains only one item, the
first item is returned.
Roughly equivalent to:
>>> def reduce(function, iterable, initializer=None):
... it = iter(iterable)
... if initializer is None:
... value = next(it)
... else:
... value = initializer
... for element in it:
... value = function(value, element)
... return value
SetUp:
>>> from functools import reduce
>>>
>>> DATA = [1, 2, 3, 4, 5]
Usage:
>>> def add(x, y):
... return (x + y)
>>>
>>> reduce(add, DATA)
15
>>> reduce(lambda x, y: x + y, DATA)
15
8.20.4. Singledispatch¶
Since Python 3.4
Overload a method
Python will choose function to run based on argument type
>>> from functools import singledispatch
>>>
>>>
>>> @singledispatch
... def celsius_to_kelvin(arg):
... raise NotImplementedError('Argument must be int or list')
>>>
>>> @celsius_to_kelvin.register
... def _(degree: int):
... return degree + 273.15
>>>
>>> @celsius_to_kelvin.register
... def _(degrees: list):
... return [d+273.15 for d in degrees]
>>>
>>>
>>> celsius_to_kelvin(1)
274.15
>>>
>>> celsius_to_kelvin([1,2])
[274.15, 275.15]
>>>
>>> celsius_to_kelvin((1,2))
Traceback (most recent call last):
NotImplementedError: Argument must be int or list
>>> from functools import singledispatch
>>>
>>>
>>> @singledispatch
... def km_to_m(km):
... raise NotImplementedError('...')
>>>
>>>
>>> @km_to_m.register
... def _(km: int):
... return km * 1000
>>>
>>>
>>> @km_to_m.register
... def _(km: float):
... return km * 1000.0
>>>
>>>
>>> @km_to_m.register
... def _(km: list):
... return [x*1000 for x in km]
8.20.5. Singledispatchmethod¶
Since Python 3.8
Overload a method
Python will choose method to run based on argument type
>>> from functools import singledispatchmethod
>>>
>>>
>>> class Converter:
...
... @singledispatchmethod
... def celsius_to_kelvin(*args):
... raise NotImplementedError('Argument must be int or list')
...
... @celsius_to_kelvin.register
... def _(self, degree: int):
... return degree + 273.15
...
... @celsius_to_kelvin.register
... def _(self, degrees: list):
... return [d+273.15 for d in degrees]
>>>
>>>
>>> conv = Converter()
>>>
>>> conv.celsius_to_kelvin(1)
274.15
>>>
>>> conv.celsius_to_kelvin([1,2])
[274.15, 275.15]
>>>
>>> conv.celsius_to_kelvin((1,2))
Traceback (most recent call last):
NotImplementedError: Argument must be int or list
8.20.6. Use Case - 0x01¶
>>> def square(x):
... return x ** 2
>>>
>>> def cube(x):
... return x ** 3
>>>
>>> def apply(data, fn):
... return map(fn, data)
>>>
>>> def add(x, y):
... return x + y
>>> data = [1,2,3,4]
>>> transformations = [square, cube]
>>> result = reduce(apply, transformations, data)
>>> list(result)
[1, 64, 729, 4096]
>>> result = reduce(apply, transformations, data)
>>> reduce(add, result)
4890