1"""
2Functional helpers needed only by the backport.
3
4``apply`` and ``compose`` exist to serve the localization decorators on
5the public API, which the stdlib does not have. Keeping them out of
6``_functools`` leaves that module identical to its
7``importlib.metadata`` counterpart.
8"""
9
10import functools
11
12
13# From jaraco.functools 4.0.2
14def compose(*funcs):
15 """
16 Compose any number of unary functions into a single unary function.
17
18 Comparable to
19 `function composition <https://en.wikipedia.org/wiki/Function_composition>`_
20 in mathematics:
21
22 ``h = g ∘ f`` implies ``h(x) = g(f(x))``.
23
24 In Python, ``h = compose(g, f)``.
25
26 >>> import textwrap
27 >>> expected = str.strip(textwrap.dedent(compose.__doc__))
28 >>> strip_and_dedent = compose(str.strip, textwrap.dedent)
29 >>> strip_and_dedent(compose.__doc__) == expected
30 True
31
32 Compose also allows the innermost function to take arbitrary arguments.
33
34 >>> round_three = lambda x: round(x, ndigits=3)
35 >>> f = compose(round_three, int.__truediv__)
36 >>> [f(3*x, x+1) for x in range(1,10)]
37 [1.5, 2.0, 2.25, 2.4, 2.5, 2.571, 2.625, 2.667, 2.7]
38 """
39
40 def compose_two(f1, f2):
41 return lambda *args, **kwargs: f1(f2(*args, **kwargs))
42
43 return functools.reduce(compose_two, funcs)
44
45
46def apply(transform):
47 """
48 Decorate a function with a transform function that is
49 invoked on results returned from the decorated function.
50
51 >>> @apply(reversed)
52 ... def get_numbers(start):
53 ... "doc for get_numbers"
54 ... return range(start, start+3)
55 >>> list(get_numbers(4))
56 [6, 5, 4]
57 >>> get_numbers.__doc__
58 'doc for get_numbers'
59 """
60
61 def wrap(func):
62 return functools.wraps(func)(compose(transform, func))
63
64 return wrap