mirror of
https://github.com/python/cpython.git
synced 2025-07-23 19:25:40 +00:00
#4378: fix a few functional HOWTO 2.xisms.
This commit is contained in:
parent
7a0f747eab
commit
4216d2d802
1 changed files with 153 additions and 156 deletions
|
@ -659,54 +659,6 @@ This can also be written as a list comprehension:
|
||||||
>>> list(x for x in range(10) if is_even(x))
|
>>> list(x for x in range(10) if is_even(x))
|
||||||
[0, 2, 4, 6, 8]
|
[0, 2, 4, 6, 8]
|
||||||
|
|
||||||
``functools.reduce(func, iter, [initial_value])`` cumulatively performs an
|
|
||||||
operation on all the iterable's elements and, therefore, can't be applied to
|
|
||||||
infinite iterables. (Note it is not in :mod:`builtins`, but in the
|
|
||||||
:mod:`functools` module.) ``func`` must be a function that takes two elements
|
|
||||||
and returns a single value. :func:`functools.reduce` takes the first two
|
|
||||||
elements A and B returned by the iterator and calculates ``func(A, B)``. It
|
|
||||||
then requests the third element, C, calculates ``func(func(A, B), C)``, combines
|
|
||||||
this result with the fourth element returned, and continues until the iterable
|
|
||||||
is exhausted. If the iterable returns no 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 first calculation. ::
|
|
||||||
|
|
||||||
>>> import operator, functools
|
|
||||||
>>> functools.reduce(operator.concat, ['A', 'BB', 'C'])
|
|
||||||
'ABBC'
|
|
||||||
>>> functools.reduce(operator.concat, [])
|
|
||||||
Traceback (most recent call last):
|
|
||||||
...
|
|
||||||
TypeError: reduce() of empty sequence with no initial value
|
|
||||||
>>> functools.reduce(operator.mul, [1,2,3], 1)
|
|
||||||
6
|
|
||||||
>>> functools.reduce(operator.mul, [], 1)
|
|
||||||
1
|
|
||||||
|
|
||||||
If you use :func:`operator.add` with :func:`functools.reduce`, you'll add up all the
|
|
||||||
elements of the iterable. This case is so common that there's a special
|
|
||||||
built-in called :func:`sum` to compute it:
|
|
||||||
|
|
||||||
>>> import functools
|
|
||||||
>>> functools.reduce(operator.add, [1,2,3,4], 0)
|
|
||||||
10
|
|
||||||
>>> sum([1,2,3,4])
|
|
||||||
10
|
|
||||||
>>> sum([])
|
|
||||||
0
|
|
||||||
|
|
||||||
For many uses of :func:`functools.reduce`, though, it can be clearer to just write the
|
|
||||||
obvious :keyword:`for` loop::
|
|
||||||
|
|
||||||
import functools
|
|
||||||
# Instead of:
|
|
||||||
product = functools.reduce(operator.mul, [1,2,3], 1)
|
|
||||||
|
|
||||||
# You can write:
|
|
||||||
product = 1
|
|
||||||
for i in [1,2,3]:
|
|
||||||
product *= i
|
|
||||||
|
|
||||||
|
|
||||||
``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. ::
|
||||||
|
@ -744,6 +696,7 @@ the constructed list's ``.sort()`` method. ::
|
||||||
(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
|
||||||
Python wiki at http://wiki.python.org/moin/HowTo/Sorting.)
|
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
|
||||||
|
@ -763,90 +716,27 @@ values:
|
||||||
True
|
True
|
||||||
|
|
||||||
|
|
||||||
Small functions and the lambda expression
|
``zip(iterA, iterB, ...)`` takes one element from each iterable and
|
||||||
=========================================
|
returns them in a tuple::
|
||||||
|
|
||||||
When writing functional-style programs, you'll often need little functions that
|
zip(['a', 'b', 'c'], (1, 2, 3)) =>
|
||||||
act as predicates or that combine elements in some way.
|
('a', 1), ('b', 2), ('c', 3)
|
||||||
|
|
||||||
If there's a Python built-in or a module function that's suitable, you don't
|
It doesn't construct an in-memory list and exhaust all the input iterators
|
||||||
need to define a new function at all::
|
before returning; instead tuples are constructed and returned only if they're
|
||||||
|
requested. (The technical term for this behaviour is `lazy evaluation
|
||||||
|
<http://en.wikipedia.org/wiki/Lazy_evaluation>`__.)
|
||||||
|
|
||||||
stripped_lines = [line.strip() for line in lines]
|
This iterator is intended to be used with iterables that are all of the same
|
||||||
existing_files = filter(os.path.exists, file_list)
|
length. If the iterables are of different lengths, the resulting stream will be
|
||||||
|
the same length as the shortest iterable. ::
|
||||||
|
|
||||||
If the function you need doesn't exist, you need to write it. One way to write
|
zip(['a', 'b'], (1, 2, 3)) =>
|
||||||
small functions is to use the ``lambda`` statement. ``lambda`` takes a number
|
('a', 1), ('b', 2)
|
||||||
of parameters and an expression combining these parameters, and creates a small
|
|
||||||
function that returns the value of the expression::
|
|
||||||
|
|
||||||
lowercase = lambda x: x.lower()
|
You should avoid doing this, though, because an element may be taken from the
|
||||||
|
longer iterators and discarded. This means you can't go on to use the iterators
|
||||||
print_assign = lambda name, value: name + '=' + str(value)
|
further because you risk skipping a discarded element.
|
||||||
|
|
||||||
adder = lambda x, y: x+y
|
|
||||||
|
|
||||||
An alternative is to just use the ``def`` statement and define a function in the
|
|
||||||
usual way::
|
|
||||||
|
|
||||||
def lowercase(x):
|
|
||||||
return x.lower()
|
|
||||||
|
|
||||||
def print_assign(name, value):
|
|
||||||
return name + '=' + str(value)
|
|
||||||
|
|
||||||
def adder(x,y):
|
|
||||||
return x + y
|
|
||||||
|
|
||||||
Which alternative is preferable? That's a style question; my usual course is to
|
|
||||||
avoid using ``lambda``.
|
|
||||||
|
|
||||||
One reason for my preference is that ``lambda`` is quite limited in the
|
|
||||||
functions it can define. The result has to be computable as a single
|
|
||||||
expression, which means you can't have multiway ``if... elif... else``
|
|
||||||
comparisons or ``try... except`` statements. If you try to do too much in a
|
|
||||||
``lambda`` statement, you'll end up with an overly complicated expression that's
|
|
||||||
hard to read. Quick, what's the following code doing?
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
import functools
|
|
||||||
total = functools.reduce(lambda a, b: (0, a[1] + b[1]), items)[1]
|
|
||||||
|
|
||||||
You can figure it out, but it takes time to disentangle the expression to figure
|
|
||||||
out what's going on. Using a short nested ``def`` statements makes things a
|
|
||||||
little bit better::
|
|
||||||
|
|
||||||
import functools
|
|
||||||
def combine (a, b):
|
|
||||||
return 0, a[1] + b[1]
|
|
||||||
|
|
||||||
total = functools.reduce(combine, items)[1]
|
|
||||||
|
|
||||||
But it would be best of all if I had simply used a ``for`` loop::
|
|
||||||
|
|
||||||
total = 0
|
|
||||||
for a, b in items:
|
|
||||||
total += b
|
|
||||||
|
|
||||||
Or the :func:`sum` built-in and a generator expression::
|
|
||||||
|
|
||||||
total = sum(b for a,b in items)
|
|
||||||
|
|
||||||
Many uses of :func:`functools.reduce` are clearer when written as ``for`` loops.
|
|
||||||
|
|
||||||
Fredrik Lundh once suggested the following set of rules for refactoring uses of
|
|
||||||
``lambda``:
|
|
||||||
|
|
||||||
1) Write a lambda function.
|
|
||||||
2) Write a comment explaining what the heck that lambda does.
|
|
||||||
3) Study the comment for a while, and think of a name that captures the essence
|
|
||||||
of the comment.
|
|
||||||
4) Convert the lambda to a def statement, using that name.
|
|
||||||
5) Remove the comment.
|
|
||||||
|
|
||||||
I really like these rules, but you're free to disagree
|
|
||||||
about whether this lambda-free style is better.
|
|
||||||
|
|
||||||
|
|
||||||
The itertools module
|
The itertools module
|
||||||
|
@ -896,29 +786,6 @@ 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
|
||||||
|
|
||||||
``itertools.izip(iterA, iterB, ...)`` takes one element from each iterable and
|
|
||||||
returns them in a tuple::
|
|
||||||
|
|
||||||
itertools.izip(['a', 'b', 'c'], (1, 2, 3)) =>
|
|
||||||
('a', 1), ('b', 2), ('c', 3)
|
|
||||||
|
|
||||||
It's similar to the built-in :func:`zip` function, but doesn't construct an
|
|
||||||
in-memory list and exhaust all the input iterators before returning; instead
|
|
||||||
tuples are constructed and returned only if they're requested. (The technical
|
|
||||||
term for this behaviour is `lazy evaluation
|
|
||||||
<http://en.wikipedia.org/wiki/Lazy_evaluation>`__.)
|
|
||||||
|
|
||||||
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
|
|
||||||
the same length as the shortest iterable. ::
|
|
||||||
|
|
||||||
itertools.izip(['a', 'b'], (1, 2, 3)) =>
|
|
||||||
('a', 1), ('b', 2)
|
|
||||||
|
|
||||||
You should avoid doing this, though, because an element may be taken from the
|
|
||||||
longer iterators and discarded. This means you can't go on to use the iterators
|
|
||||||
further because you risk skipping a discarded element.
|
|
||||||
|
|
||||||
``itertools.islice(iter, [start], stop, [step])`` returns a stream that's a
|
``itertools.islice(iter, [start], stop, [step])`` returns a stream that's a
|
||||||
slice of the iterator. With a single ``stop`` argument, it will return the
|
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
|
||||||
|
@ -953,8 +820,6 @@ consumed more than the others. ::
|
||||||
Calling functions on elements
|
Calling functions on elements
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
``itertools.imap(func, iter)`` is the same as built-in :func:`map`.
|
|
||||||
|
|
||||||
The ``operator`` module contains a set of functions corresponding to Python's
|
The ``operator`` module contains a set of functions corresponding to Python's
|
||||||
operators. Some examples are ``operator.add(a, b)`` (adds two values),
|
operators. Some examples are ``operator.add(a, b)`` (adds two values),
|
||||||
``operator.ne(a, b)`` (same as ``a!=b``), and ``operator.attrgetter('id')``
|
``operator.ne(a, b)`` (same as ``a!=b``), and ``operator.attrgetter('id')``
|
||||||
|
@ -976,12 +841,10 @@ Selecting elements
|
||||||
Another group of functions chooses a subset of an iterator's elements based on a
|
Another group of functions chooses a subset of an iterator's elements based on a
|
||||||
predicate.
|
predicate.
|
||||||
|
|
||||||
``itertools.ifilter(predicate, iter)`` is the same as built-in :func:`filter`.
|
``itertools.filterfalse(predicate, iter)`` is the opposite, returning all
|
||||||
|
|
||||||
``itertools.ifilterfalse(predicate, iter)`` is the opposite, returning all
|
|
||||||
elements for which the predicate returns false::
|
elements for which the predicate returns false::
|
||||||
|
|
||||||
itertools.ifilterfalse(is_even, itertools.count()) =>
|
itertools.filterfalse(is_even, itertools.count()) =>
|
||||||
1, 3, 5, 7, 9, 11, 13, 15, ...
|
1, 3, 5, 7, 9, 11, 13, 15, ...
|
||||||
|
|
||||||
``itertools.takewhile(predicate, iter)`` returns elements for as long as the
|
``itertools.takewhile(predicate, iter)`` returns elements for as long as the
|
||||||
|
@ -1083,6 +946,54 @@ Here's a small but realistic example::
|
||||||
server_log = functools.partial(log, subsystem='server')
|
server_log = functools.partial(log, subsystem='server')
|
||||||
server_log('Unable to open socket')
|
server_log('Unable to open socket')
|
||||||
|
|
||||||
|
``functools.reduce(func, iter, [initial_value])`` cumulatively performs an
|
||||||
|
operation on all the iterable's elements and, therefore, can't be applied to
|
||||||
|
infinite iterables. (Note it is not in :mod:`builtins`, but in the
|
||||||
|
:mod:`functools` module.) ``func`` must be a function that takes two elements
|
||||||
|
and returns a single value. :func:`functools.reduce` takes the first two
|
||||||
|
elements A and B returned by the iterator and calculates ``func(A, B)``. It
|
||||||
|
then requests the third element, C, calculates ``func(func(A, B), C)``, combines
|
||||||
|
this result with the fourth element returned, and continues until the iterable
|
||||||
|
is exhausted. If the iterable returns no 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 first calculation. ::
|
||||||
|
|
||||||
|
>>> import operator, functools
|
||||||
|
>>> functools.reduce(operator.concat, ['A', 'BB', 'C'])
|
||||||
|
'ABBC'
|
||||||
|
>>> functools.reduce(operator.concat, [])
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
TypeError: reduce() of empty sequence with no initial value
|
||||||
|
>>> functools.reduce(operator.mul, [1,2,3], 1)
|
||||||
|
6
|
||||||
|
>>> functools.reduce(operator.mul, [], 1)
|
||||||
|
1
|
||||||
|
|
||||||
|
If you use :func:`operator.add` with :func:`functools.reduce`, you'll add up all the
|
||||||
|
elements of the iterable. This case is so common that there's a special
|
||||||
|
built-in called :func:`sum` to compute it:
|
||||||
|
|
||||||
|
>>> import functools
|
||||||
|
>>> functools.reduce(operator.add, [1,2,3,4], 0)
|
||||||
|
10
|
||||||
|
>>> sum([1,2,3,4])
|
||||||
|
10
|
||||||
|
>>> sum([])
|
||||||
|
0
|
||||||
|
|
||||||
|
For many uses of :func:`functools.reduce`, though, it can be clearer to just write the
|
||||||
|
obvious :keyword:`for` loop::
|
||||||
|
|
||||||
|
import functools
|
||||||
|
# Instead of:
|
||||||
|
product = functools.reduce(operator.mul, [1,2,3], 1)
|
||||||
|
|
||||||
|
# You can write:
|
||||||
|
product = 1
|
||||||
|
for i in [1,2,3]:
|
||||||
|
product *= i
|
||||||
|
|
||||||
|
|
||||||
The operator module
|
The operator module
|
||||||
-------------------
|
-------------------
|
||||||
|
@ -1232,6 +1143,92 @@ idiom::
|
||||||
join = partial(foldl, concat, "")
|
join = partial(foldl, concat, "")
|
||||||
|
|
||||||
|
|
||||||
|
Small functions and the lambda expression
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
When writing functional-style programs, you'll often need little functions that
|
||||||
|
act as predicates or that combine elements in some way.
|
||||||
|
|
||||||
|
If there's a Python built-in or a module function that's suitable, you don't
|
||||||
|
need to define a new function at all::
|
||||||
|
|
||||||
|
stripped_lines = [line.strip() for line in lines]
|
||||||
|
existing_files = filter(os.path.exists, file_list)
|
||||||
|
|
||||||
|
If the function you need doesn't exist, you need to write it. One way to write
|
||||||
|
small functions is to use the ``lambda`` statement. ``lambda`` takes a number
|
||||||
|
of parameters and an expression combining these parameters, and creates a small
|
||||||
|
function that returns the value of the expression::
|
||||||
|
|
||||||
|
lowercase = lambda x: x.lower()
|
||||||
|
|
||||||
|
print_assign = lambda name, value: name + '=' + str(value)
|
||||||
|
|
||||||
|
adder = lambda x, y: x+y
|
||||||
|
|
||||||
|
An alternative is to just use the ``def`` statement and define a function in the
|
||||||
|
usual way::
|
||||||
|
|
||||||
|
def lowercase(x):
|
||||||
|
return x.lower()
|
||||||
|
|
||||||
|
def print_assign(name, value):
|
||||||
|
return name + '=' + str(value)
|
||||||
|
|
||||||
|
def adder(x,y):
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
Which alternative is preferable? That's a style question; my usual course is to
|
||||||
|
avoid using ``lambda``.
|
||||||
|
|
||||||
|
One reason for my preference is that ``lambda`` is quite limited in the
|
||||||
|
functions it can define. The result has to be computable as a single
|
||||||
|
expression, which means you can't have multiway ``if... elif... else``
|
||||||
|
comparisons or ``try... except`` statements. If you try to do too much in a
|
||||||
|
``lambda`` statement, you'll end up with an overly complicated expression that's
|
||||||
|
hard to read. Quick, what's the following code doing?
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
import functools
|
||||||
|
total = functools.reduce(lambda a, b: (0, a[1] + b[1]), items)[1]
|
||||||
|
|
||||||
|
You can figure it out, but it takes time to disentangle the expression to figure
|
||||||
|
out what's going on. Using a short nested ``def`` statements makes things a
|
||||||
|
little bit better::
|
||||||
|
|
||||||
|
import functools
|
||||||
|
def combine (a, b):
|
||||||
|
return 0, a[1] + b[1]
|
||||||
|
|
||||||
|
total = functools.reduce(combine, items)[1]
|
||||||
|
|
||||||
|
But it would be best of all if I had simply used a ``for`` loop::
|
||||||
|
|
||||||
|
total = 0
|
||||||
|
for a, b in items:
|
||||||
|
total += b
|
||||||
|
|
||||||
|
Or the :func:`sum` built-in and a generator expression::
|
||||||
|
|
||||||
|
total = sum(b for a,b in items)
|
||||||
|
|
||||||
|
Many uses of :func:`functools.reduce` are clearer when written as ``for`` loops.
|
||||||
|
|
||||||
|
Fredrik Lundh once suggested the following set of rules for refactoring uses of
|
||||||
|
``lambda``:
|
||||||
|
|
||||||
|
1) Write a lambda function.
|
||||||
|
2) Write a comment explaining what the heck that lambda does.
|
||||||
|
3) Study the comment for a while, and think of a name that captures the essence
|
||||||
|
of the comment.
|
||||||
|
4) Convert the lambda to a def statement, using that name.
|
||||||
|
5) Remove the comment.
|
||||||
|
|
||||||
|
I really like these rules, but you're free to disagree
|
||||||
|
about whether this lambda-free style is better.
|
||||||
|
|
||||||
|
|
||||||
Revision History and Acknowledgements
|
Revision History and Acknowledgements
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue