Fixup this HOWTO's doctest blocks so that they can be run with sphinx' doctest builder.

This commit is contained in:
Georg Brandl 2008-03-22 11:00:48 +00:00
parent b98fe5a87f
commit 09a7fe6933

View file

@ -2,7 +2,7 @@
Functional Programming HOWTO Functional Programming HOWTO
******************************** ********************************
:Author: \A. M. Kuchling :Author: A. M. Kuchling
:Release: 0.31 :Release: 0.31
(This is a first draft. Please send comments/error reports/suggestions to (This is a first draft. Please send comments/error reports/suggestions to
@ -98,6 +98,7 @@ to the functional style:
* Composability. * Composability.
* Ease of debugging and testing. * Ease of debugging and testing.
Formal provability Formal provability
------------------ ------------------
@ -133,6 +134,7 @@ down or generated a proof, there would then be the question of verifying the
proof; maybe there's an error in it, and you wrongly believe you've proved the proof; maybe there's an error in it, and you wrongly believe you've proved the
program correct. program correct.
Modularity Modularity
---------- ----------
@ -159,7 +161,6 @@ running a test; instead you only have to synthesize the right input and then
check that the output matches expectations. check that the output matches expectations.
Composability Composability
------------- -------------
@ -175,7 +176,6 @@ new programs by arranging existing functions in a new configuration and writing
a few functions specialized for the current task. a few functions specialized for the current task.
Iterators Iterators
========= =========
@ -197,12 +197,12 @@ built-in data types support iteration, the most common being lists and
dictionaries. An object is called an **iterable** object if you can get an dictionaries. An object is called an **iterable** object if you can get an
iterator for it. iterator for it.
You can experiment with the iteration interface manually:: You can experiment with the iteration interface manually:
>>> L = [1,2,3] >>> L = [1,2,3]
>>> it = iter(L) >>> it = iter(L)
>>> print it >>> print it
<iterator object at 0x8116870> <...iterator object at ...>
>>> it.next() >>> it.next()
1 1
>>> it.next() >>> it.next()
@ -227,7 +227,7 @@ These two statements are equivalent::
print i print i
Iterators can be materialized as lists or tuples by using the :func:`list` or Iterators can be materialized as lists or tuples by using the :func:`list` or
:func:`tuple` constructor functions:: :func:`tuple` constructor functions:
>>> L = [1,2,3] >>> L = [1,2,3]
>>> iterator = iter(L) >>> iterator = iter(L)
@ -236,7 +236,7 @@ Iterators can be materialized as lists or tuples by using the :func:`list` or
(1, 2, 3) (1, 2, 3)
Sequence unpacking also supports iterators: if you know an iterator will return Sequence unpacking also supports iterators: if you know an iterator will return
N elements, you can unpack them into an N-tuple:: N elements, you can unpack them into an N-tuple:
>>> L = [1,2,3] >>> L = [1,2,3]
>>> iterator = iter(L) >>> iterator = iter(L)
@ -269,7 +269,7 @@ sequence type, such as strings, will automatically support creation of an
iterator. iterator.
Calling :func:`iter` on a dictionary returns an iterator that will loop over the Calling :func:`iter` on a dictionary returns an iterator that will loop over the
dictionary's keys:: dictionary's keys:
>>> m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6, >>> m = {'Jan': 1, 'Feb': 2, 'Mar': 3, 'Apr': 4, 'May': 5, 'Jun': 6,
... 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12} ... 'Jul': 7, 'Aug': 8, 'Sep': 9, 'Oct': 10, 'Nov': 11, 'Dec': 12}
@ -279,11 +279,11 @@ dictionary's keys::
Feb 2 Feb 2
Aug 8 Aug 8
Sep 9 Sep 9
May 5 Apr 4
Jun 6 Jun 6
Jul 7 Jul 7
Jan 1 Jan 1
Apr 4 May 5
Nov 11 Nov 11
Dec 12 Dec 12
Oct 10 Oct 10
@ -297,7 +297,7 @@ values, or key/value pairs, you can explicitly call the ``iterkeys()``,
``itervalues()``, or ``iteritems()`` methods to get an appropriate iterator. ``itervalues()``, or ``iteritems()`` methods to get an appropriate iterator.
The :func:`dict` constructor can accept an iterator that returns a finite stream The :func:`dict` constructor can accept an iterator that returns a finite stream
of ``(key, value)`` tuples:: of ``(key, value)`` tuples:
>>> L = [('Italy', 'Rome'), ('France', 'Paris'), ('US', 'Washington DC')] >>> L = [('Italy', 'Rome'), ('France', 'Paris'), ('US', 'Washington DC')]
>>> dict(iter(L)) >>> dict(iter(L))
@ -406,11 +406,14 @@ equivalent to the following Python code::
This means that when there are multiple ``for...in`` clauses but no ``if`` This means that when there are multiple ``for...in`` clauses but no ``if``
clauses, the length of the resulting output will be equal to the product of the clauses, the length of the resulting output will be equal to the product of the
lengths of all the sequences. If you have two lists of length 3, the output lengths of all the sequences. If you have two lists of length 3, the output
list is 9 elements long:: list is 9 elements long:
seq1 = 'abc' .. doctest::
seq2 = (1,2,3) :options: +NORMALIZE_WHITESPACE
>>> [ (x,y) for x in seq1 for y in seq2]
>>> seq1 = 'abc'
>>> seq2 = (1,2,3)
>>> [(x,y) for x in seq1 for y in seq2]
[('a', 1), ('a', 2), ('a', 3), [('a', 1), ('a', 2), ('a', 3),
('b', 1), ('b', 2), ('b', 3), ('b', 1), ('b', 2), ('b', 3),
('c', 1), ('c', 2), ('c', 3)] ('c', 1), ('c', 2), ('c', 3)]
@ -441,7 +444,9 @@ variables. But, what if the local variables weren't thrown away on exiting a
function? What if you could later resume the function where it left off? This function? What if you could later resume the function where it left off? This
is what generators provide; they can be thought of as resumable functions. is what generators provide; they can be thought of as resumable functions.
Here's the simplest example of a generator function:: Here's the simplest example of a generator function:
.. testcode:: doctest_block
def generate_ints(N): def generate_ints(N):
for i in range(N): for i in range(N):
@ -459,11 +464,11 @@ statement is that on reaching a ``yield`` the generator's state of execution is
suspended and local variables are preserved. On the next call to the suspended and local variables are preserved. On the next call to the
generator's ``.next()`` method, the function will resume executing. generator's ``.next()`` method, the function will resume executing.
Here's a sample usage of the ``generate_ints()`` generator:: Here's a sample usage of the ``generate_ints()`` generator:
>>> gen = generate_ints(3) >>> gen = generate_ints(3)
>>> gen >>> gen
<generator object at 0x8117f90> <generator object at ...>
>>> gen.next() >>> gen.next()
0 0
>>> gen.next() >>> gen.next()
@ -496,9 +501,7 @@ can be much messier.
The test suite included with Python's library, ``test_generators.py``, contains The test suite included with Python's library, ``test_generators.py``, contains
a number of more interesting examples. Here's one generator that implements an a number of more interesting examples. Here's one generator that implements an
in-order traversal of a tree using generators recursively. in-order traversal of a tree using generators recursively. ::
::
# A recursive generator that generates Tree leaves in in-order. # A recursive generator that generates Tree leaves in in-order.
def inorder(t): def inorder(t):
@ -553,7 +556,7 @@ returns ``None``.
Here's a simple counter that increments by 1 and allows changing the value of Here's a simple counter that increments by 1 and allows changing the value of
the internal counter. the internal counter.
:: .. testcode:: doctest_block
def counter (maximum): def counter (maximum):
i = 0 i = 0
@ -623,14 +626,13 @@ lists instead of iterators.
``map(f, iterA, iterB, ...)`` returns a list containing ``f(iterA[0], iterB[0]), ``map(f, iterA, iterB, ...)`` returns a list containing ``f(iterA[0], iterB[0]),
f(iterA[1], iterB[1]), f(iterA[2], iterB[2]), ...``. f(iterA[1], iterB[1]), f(iterA[2], iterB[2]), ...``.
:: >>> def upper(s):
... return s.upper()
def upper(s): >>> map(upper, ['sentence', 'fragment'])
return s.upper()
map(upper, ['sentence', 'fragment']) =>
['SENTENCE', 'FRAGMENT'] ['SENTENCE', 'FRAGMENT']
[upper(s) for s in ['sentence', 'fragment']] => >>> [upper(s) for s in ['sentence', 'fragment']]
['SENTENCE', 'FRAGMENT'] ['SENTENCE', 'FRAGMENT']
As shown above, you can achieve the same effect with a list comprehension. The As shown above, you can achieve the same effect with a list comprehension. The
@ -643,15 +645,13 @@ comprehensions. A **predicate** is a function that returns the truth value of
some condition; for use with :func:`filter`, the predicate must take a single some condition; for use with :func:`filter`, the predicate must take a single
value. value.
:: >>> def is_even(x):
... return (x % 2) == 0
def is_even(x): >>> filter(is_even, range(10))
return (x % 2) == 0
filter(is_even, range(10)) =>
[0, 2, 4, 6, 8] [0, 2, 4, 6, 8]
This can also be written as a list comprehension:: This can also be written as a list comprehension:
>>> [x for x in range(10) if is_even(x)] >>> [x for x in range(10) if is_even(x)]
[0, 2, 4, 6, 8] [0, 2, 4, 6, 8]
@ -672,27 +672,27 @@ values at all, a :exc:`TypeError` exception is raised. If the initial value is
supplied, it's used as a starting point and ``func(initial_value, A)`` is the supplied, it's used as a starting point and ``func(initial_value, A)`` is the
first calculation. first calculation.
:: >>> import operator
>>> reduce(operator.concat, ['A', 'BB', 'C'])
import operator
reduce(operator.concat, ['A', 'BB', 'C']) =>
'ABBC' 'ABBC'
reduce(operator.concat, []) => >>> reduce(operator.concat, [])
Traceback (most recent call last):
...
TypeError: reduce() of empty sequence with no initial value TypeError: reduce() of empty sequence with no initial value
reduce(operator.mul, [1,2,3], 1) => >>> reduce(operator.mul, [1,2,3], 1)
6 6
reduce(operator.mul, [], 1) => >>> reduce(operator.mul, [], 1)
1 1
If you use :func:`operator.add` with :func:`reduce`, you'll add up all the If you use :func:`operator.add` with :func:`reduce`, you'll add up all the
elements of the iterable. This case is so common that there's a special elements of the iterable. This case is so common that there's a special
built-in called :func:`sum` to compute it:: built-in called :func:`sum` to compute it:
reduce(operator.add, [1,2,3,4], 0) => >>> reduce(operator.add, [1,2,3,4], 0)
10 10
sum([1,2,3,4]) => >>> sum([1,2,3,4])
10 10
sum([]) => >>> sum([])
0 0
For many uses of :func:`reduce`, though, it can be clearer to just write the For many uses of :func:`reduce`, though, it can be clearer to just write the
@ -710,10 +710,11 @@ obvious :keyword:`for` loop::
``enumerate(iter)`` counts off the elements in the iterable, returning 2-tuples ``enumerate(iter)`` counts off the elements in the iterable, returning 2-tuples
containing the count and each element. containing the count and each element.
:: >>> for item in enumerate(['subject', 'verb', 'object']):
... print item
enumerate(['subject', 'verb', 'object']) => (0, 'subject')
(0, 'subject'), (1, 'verb'), (2, 'object') (1, 'verb')
(2, 'object')
:func:`enumerate` is often used when looping through a list and recording the :func:`enumerate` is often used when looping through a list and recording the
indexes at which certain conditions are met:: indexes at which certain conditions are met::
@ -726,18 +727,16 @@ indexes at which certain conditions are met::
``sorted(iterable, [cmp=None], [key=None], [reverse=False)`` collects all the ``sorted(iterable, [cmp=None], [key=None], [reverse=False)`` collects all the
elements of the iterable into a list, sorts the list, and returns the sorted elements of the iterable into a list, sorts the list, and returns the sorted
result. The ``cmp``, ``key``, and ``reverse`` arguments are passed through to result. The ``cmp``, ``key``, and ``reverse`` arguments are passed through to
the constructed list's ``.sort()`` method. the constructed list's ``.sort()`` method. ::
:: >>> import random
>>> # Generate 8 random numbers between [0, 10000)
import random >>> rand_list = random.sample(range(10000), 8)
# Generate 8 random numbers between [0, 10000) >>> rand_list
rand_list = random.sample(range(10000), 8)
rand_list =>
[769, 7953, 9828, 6431, 8442, 9878, 6213, 2207] [769, 7953, 9828, 6431, 8442, 9878, 6213, 2207]
sorted(rand_list) => >>> sorted(rand_list)
[769, 2207, 6213, 6431, 7953, 8442, 9828, 9878] [769, 2207, 6213, 6431, 7953, 8442, 9828, 9878]
sorted(rand_list, reverse=True) => >>> sorted(rand_list, reverse=True)
[9878, 9828, 8442, 7953, 6431, 6213, 2207, 769] [9878, 9828, 8442, 7953, 6431, 6213, 2207, 769]
(For a more detailed discussion of sorting, see the Sorting mini-HOWTO in the (For a more detailed discussion of sorting, see the Sorting mini-HOWTO in the
@ -746,19 +745,19 @@ Python wiki at http://wiki.python.org/moin/HowTo/Sorting.)
The ``any(iter)`` and ``all(iter)`` built-ins look at the truth values of an The ``any(iter)`` and ``all(iter)`` built-ins look at the truth values of an
iterable's contents. :func:`any` returns True if any element in the iterable is iterable's contents. :func:`any` returns True if any element in the iterable is
a true value, and :func:`all` returns True if all of the elements are true a true value, and :func:`all` returns True if all of the elements are true
values:: values:
any([0,1,0]) => >>> any([0,1,0])
True True
any([0,0,0]) => >>> any([0,0,0])
False False
any([1,1,1]) => >>> any([1,1,1])
True True
all([0,1,0]) => >>> all([0,1,0])
False False
all([0,0,0]) => >>> all([0,0,0])
False False
all([1,1,1]) => >>> all([1,1,1])
True True
@ -873,17 +872,13 @@ each time. You can optionally supply the starting number, which defaults to 0::
``itertools.cycle(iter)`` saves a copy of the contents of a provided iterable ``itertools.cycle(iter)`` saves a copy of the contents of a provided iterable
and returns a new iterator that returns its elements from first to last. The and returns a new iterator that returns its elements from first to last. The
new iterator will repeat these elements infinitely. new iterator will repeat these elements infinitely. ::
::
itertools.cycle([1,2,3,4,5]) => itertools.cycle([1,2,3,4,5]) =>
1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ... 1, 2, 3, 4, 5, 1, 2, 3, 4, 5, ...
``itertools.repeat(elem, [n])`` returns the provided element ``n`` times, or ``itertools.repeat(elem, [n])`` returns the provided element ``n`` times, or
returns the element endlessly if ``n`` is not provided. returns the element endlessly if ``n`` is not provided. ::
::
itertools.repeat('abc') => itertools.repeat('abc') =>
abc, abc, abc, abc, abc, abc, abc, abc, abc, abc, ... abc, abc, abc, abc, abc, abc, abc, abc, abc, abc, ...
@ -892,9 +887,7 @@ returns the element endlessly if ``n`` is not provided.
``itertools.chain(iterA, iterB, ...)`` takes an arbitrary number of iterables as ``itertools.chain(iterA, iterB, ...)`` takes an arbitrary number of iterables as
input, and returns all the elements of the first iterator, then all the elements input, and returns all the elements of the first iterator, then all the elements
of the second, and so on, until all of the iterables have been exhausted. of the second, and so on, until all of the iterables have been exhausted. ::
::
itertools.chain(['a', 'b', 'c'], (1, 2, 3)) => itertools.chain(['a', 'b', 'c'], (1, 2, 3)) =>
a, b, c, 1, 2, 3 a, b, c, 1, 2, 3
@ -913,9 +906,7 @@ term for this behaviour is `lazy evaluation
This iterator is intended to be used with iterables that are all of the same This iterator is intended to be used with iterables that are all of the same
length. If the iterables are of different lengths, the resulting stream will be length. If the iterables are of different lengths, the resulting stream will be
the same length as the shortest iterable. the same length as the shortest iterable. ::
::
itertools.izip(['a', 'b'], (1, 2, 3)) => itertools.izip(['a', 'b'], (1, 2, 3)) =>
('a', 1), ('b', 2) ('a', 1), ('b', 2)
@ -929,9 +920,7 @@ slice of the iterator. With a single ``stop`` argument, it will return the
first ``stop`` elements. If you supply a starting index, you'll get first ``stop`` elements. If you supply a starting index, you'll get
``stop-start`` elements, and if you supply a value for ``step``, elements will ``stop-start`` elements, and if you supply a value for ``step``, elements will
be skipped accordingly. Unlike Python's string and list slicing, you can't use be skipped accordingly. Unlike Python's string and list slicing, you can't use
negative values for ``start``, ``stop``, or ``step``. negative values for ``start``, ``stop``, or ``step``. ::
::
itertools.islice(range(10), 8) => itertools.islice(range(10), 8) =>
0, 1, 2, 3, 4, 5, 6, 7 0, 1, 2, 3, 4, 5, 6, 7
@ -945,9 +934,7 @@ independent iterators that will all return the contents of the source iterator.
If you don't supply a value for ``n``, the default is 2. Replicating iterators If you don't supply a value for ``n``, the default is 2. Replicating iterators
requires saving some of the contents of the source iterator, so this can consume requires saving some of the contents of the source iterator, so this can consume
significant memory if the iterator is large and one of the new iterators is significant memory if the iterator is large and one of the new iterators is
consumed more than the others. consumed more than the others. ::
::
itertools.tee( itertools.count() ) => itertools.tee( itertools.count() ) =>
iterA, iterB iterA, iterB
@ -1144,9 +1131,7 @@ This section contains an introduction to some of the most important functions in
The ``compose()`` function implements function composition. In other words, it The ``compose()`` function implements function composition. In other words, it
returns a wrapper around the ``outer`` and ``inner`` callables, such that the returns a wrapper around the ``outer`` and ``inner`` callables, such that the
return value from ``inner`` is fed directly to ``outer``. That is, return value from ``inner`` is fed directly to ``outer``. That is, ::
::
>>> def add(a, b): >>> def add(a, b):
... return a + b ... return a + b
@ -1157,9 +1142,7 @@ return value from ``inner`` is fed directly to ``outer``. That is,
>>> compose(double, add)(5, 6) >>> compose(double, add)(5, 6)
22 22
is equivalent to is equivalent to ::
::
>>> double(add(5, 6)) >>> double(add(5, 6))
22 22
@ -1169,9 +1152,7 @@ are not always `fully curried <http://en.wikipedia.org/wiki/Currying>`__. By
default, it is expected that the ``inner`` function will return a single object default, it is expected that the ``inner`` function will return a single object
and that the ``outer`` function will take a single argument. Setting the and that the ``outer`` function will take a single argument. Setting the
``unpack`` argument causes ``compose`` to expect a tuple from ``inner`` which ``unpack`` argument causes ``compose`` to expect a tuple from ``inner`` which
will be expanded before being passed to ``outer``. Put simply, will be expanded before being passed to ``outer``. Put simply, ::
::
compose(f, g)(5, 6) compose(f, g)(5, 6)
@ -1179,9 +1160,7 @@ is equivalent to::
f(g(5, 6)) f(g(5, 6))
while while ::
::
compose(f, g, unpack=True)(5, 6) compose(f, g, unpack=True)(5, 6)
@ -1192,9 +1171,7 @@ is equivalent to::
Even though ``compose()`` only accepts two functions, it's trivial to build up a Even though ``compose()`` only accepts two functions, it's trivial to build up a
version that will compose any number of functions. We'll use ``reduce()``, version that will compose any number of functions. We'll use ``reduce()``,
``compose()`` and ``partial()`` (the last of which is provided by both ``compose()`` and ``partial()`` (the last of which is provided by both
``functional`` and ``functools``). ``functional`` and ``functools``). ::
::
from functional import compose, partial from functional import compose, partial
@ -1212,9 +1189,7 @@ We can also use ``map()``, ``compose()`` and ``partial()`` to craft a version of
``flip(func)`` ``flip(func)``
``flip()`` wraps the callable in ``func`` and causes it to receive its ``flip()`` wraps the callable in ``func`` and causes it to receive its
non-keyword arguments in reverse order. non-keyword arguments in reverse order. ::
::
>>> def triple(a, b, c): >>> def triple(a, b, c):
... return (a, b, c) ... return (a, b, c)
@ -1260,8 +1235,7 @@ We can use ``foldl()``, ``operator.concat()`` and ``partial()`` to write a
cleaner, more aesthetically-pleasing version of Python's ``"".join(...)`` cleaner, more aesthetically-pleasing version of Python's ``"".join(...)``
idiom:: idiom::
from functional import foldl, partial from functional import foldl, partial from operator import concat
from operator import concat
join = partial(foldl, concat, "") join = partial(foldl, concat, "")