]> granicus.if.org Git - python/commitdiff
Part of SF patch #1513870 (the still relevant part) -- add reduce() to
authorGuido van Rossum <guido@python.org>
Sat, 26 Aug 2006 20:49:04 +0000 (20:49 +0000)
committerGuido van Rossum <guido@python.org>
Sat, 26 Aug 2006 20:49:04 +0000 (20:49 +0000)
functools, and adjust docs etc.

Doc/howto/doanddont.tex
Doc/lib/libfuncs.tex
Doc/lib/libfunctools.tex
Doc/tut/tut.tex
Lib/functools.py
Lib/test/test_functools.py
Misc/Vim/python.vim
Misc/cheatsheet
Misc/python-mode.el
Modules/_functoolsmodule.c
Python/Python-ast.c

index a105ca1fceee7a683194d4c53b9e0139d1801143..df3ca346a4998d6c5ff70cade6f45d0c86fb9188 100644 (file)
@@ -289,19 +289,7 @@ There are also many useful builtin functions people seem not to be
 aware of for some reason: \function{min()} and \function{max()} can
 find the minimum/maximum of any sequence with comparable semantics,
 for example, yet many people write their own
-\function{max()}/\function{min()}. Another highly useful function is
-\function{reduce()}. A classical use of \function{reduce()}
-is something like
-
-\begin{verbatim}
-import sys, operator
-nums = map(float, sys.argv[1:])
-print reduce(operator.add, nums)/len(nums)
-\end{verbatim}
-
-This cute little script prints the average of all numbers given on the
-command line. The \function{reduce()} adds up all the numbers, and
-the rest is just some pre- and postprocessing.
+\function{max()}/\function{min()}.
 
 On the same note, note that \function{float()}, \function{int()} and
 \function{long()} all accept arguments of type string, and so are
index 65b0bf538174ce8d8be7eaefc1b4ae07de8aabab..c9e35b57e7265ba4c92903f2799a8d844b39c02c 100644 (file)
@@ -836,19 +836,6 @@ class Parrot(object):
 \end{verbatim}
 \end{funcdesc}
 
-\begin{funcdesc}{reduce}{function, sequence\optional{, initializer}}
-  Apply \var{function} of two arguments cumulatively to the items of
-  \var{sequence}, from left to right, so as to reduce the sequence to
-  a single value.  For example, \code{reduce(lambda x, y: x+y, [1, 2,
-  3, 4, 5])} calculates \code{((((1+2)+3)+4)+5)}.  The left argument,
-  \var{x}, is the accumulated value and the right argument, \var{y},
-  is the update value from the \var{sequence}.  If the optional
-  \var{initializer} is present, it is placed before the items of the
-  sequence in the calculation, and serves as a default when the
-  sequence is empty.  If \var{initializer} is not given and
-  \var{sequence} contains only one item, the first item is returned.
-\end{funcdesc}
-
 \begin{funcdesc}{reload}{module}
   Reload a previously imported \var{module}.  The
   argument must be a module object, so it must have been successfully
@@ -1058,8 +1045,6 @@ class C:
   The \var{sequence}'s items are normally numbers, and are not allowed
   to be strings.  The fast, correct way to concatenate sequence of
   strings is by calling \code{''.join(\var{sequence})}.
-  Note that \code{sum(range(\var{n}), \var{m})} is equivalent to
-  \code{reduce(operator.add, range(\var{n}), \var{m})}
   \versionadded{2.3}
 \end{funcdesc}
 
index 33a6f529640cc54f4230cd23d88544c0a72463da..034143a50cd8c00736859267c35f3a73f663b311 100644 (file)
@@ -51,6 +51,19 @@ two:
   \end{verbatim}
 \end{funcdesc}
 
+\begin{funcdesc}{reduce}{function, sequence\optional{, initializer}}
+  Apply \var{function} of two arguments cumulatively to the items of
+  \var{sequence}, from left to right, so as to reduce the sequence to
+  a single value.  For example, \code{reduce(lambda x, y: x+y, [1, 2,
+  3, 4, 5])} calculates \code{((((1+2)+3)+4)+5)}.  The left argument,
+  \var{x}, is the accumulated value and the right argument, \var{y},
+  is the update value from the \var{sequence}.  If the optional
+  \var{initializer} is present, it is placed before the items of the
+  sequence in the calculation, and serves as a default when the
+  sequence is empty.  If \var{initializer} is not given and
+  \var{sequence} contains only one item, the first item is returned.
+\end{funcdesc}
+
 \begin{funcdesc}{update_wrapper}
 {wrapper, wrapped\optional{, assigned}\optional{, updated}}
 Update a wrapper function to look like the wrapped function. The optional
index 1b08a8e3a80c852ff0942d05c8054815099becf9..6f6fe6ffaae32175b6ff71ed34db16fa7de3ca99 100644 (file)
@@ -1893,8 +1893,8 @@ use \method{pop()} with \code{0} as the index.  For example:
 
 \subsection{Functional Programming Tools \label{functional}}
 
-There are three built-in functions that are very useful when used with
-lists: \function{filter()}, \function{map()}, and \function{reduce()}.
+There are two built-in functions that are very useful when used with
+lists: \function{filter()} and \function{map()}.
 
 \samp{filter(\var{function}, \var{sequence})} returns a sequence
 consisting of those items from the
@@ -1934,42 +1934,6 @@ is shorter than another).  For example:
 >>> map(add, seq, seq)
 [0, 2, 4, 6, 8, 10, 12, 14]
 \end{verbatim}
-
-\samp{reduce(\var{function}, \var{sequence})} returns a single value
-constructed by calling the binary function \var{function} on the first two
-items of the sequence, then on the result and the next item, and so
-on.  For example, to compute the sum of the numbers 1 through 10:
-
-\begin{verbatim}
->>> def add(x,y): return x+y
-...
->>> reduce(add, range(1, 11))
-55
-\end{verbatim}
-
-If there's only one item in the sequence, its value is returned; if
-the sequence is empty, an exception is raised.
-
-A third argument can be passed to indicate the starting value.  In this
-case the starting value is returned for an empty sequence, and the
-function is first applied to the starting value and the first sequence
-item, then to the result and the next item, and so on.  For example,
-
-\begin{verbatim}
->>> def sum(seq):
-...     def add(x,y): return x+y
-...     return reduce(add, seq, 0)
-... 
->>> sum(range(1, 11))
-55
->>> sum([])
-0
-\end{verbatim}
-
-Don't use this example's definition of \function{sum()}: since summing
-numbers is such a common need, a built-in function
-\code{sum(\var{sequence})} is already provided, and works exactly like
-this.
 \versionadded{2.3}
 
 \subsection{List Comprehensions}
@@ -2739,7 +2703,7 @@ standard module \module{__builtin__}\refbimodindex{__builtin__}:
  'id', 'int', 'intern', 'isinstance', 'issubclass', 'iter',
  'len', 'license', 'list', 'locals', 'long', 'map', 'max', 'min',
  'object', 'oct', 'open', 'ord', 'pow', 'property', 'quit', 'range',
- 'reduce', 'reload', 'repr', 'reversed', 'round', 'set',
+ 'reload', 'repr', 'reversed', 'round', 'set',
  'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super',
  'tuple', 'type', 'unichr', 'unicode', 'vars', 'xrange', 'zip']
 \end{verbatim}
index 8783f08488e181715d98a1f514db577180c167f5..12a527e3608cc177d6fc40a637d3b4fee83ada53 100644 (file)
@@ -7,7 +7,7 @@
 #   Copyright (C) 2006 Python Software Foundation.
 # See C source code for _functools credits/copyright
 
-from _functools import partial
+from _functools import partial, reduce
 
 # update_wrapper() and wraps() are tools to help write
 # wrapper functions that can handle naive introspection
index 8dc185b721033844b6c24fc338c9f3c13890157c..01d6cd29e8a62c8c6e5578001b5e75b59c06d66c 100644 (file)
@@ -259,6 +259,74 @@ class TestWraps(TestUpdateWrapper):
         self.assertEqual(wrapper.attr, 'This is a different test')
         self.assertEqual(wrapper.dict_attr, f.dict_attr)
 
+class TestReduce(unittest.TestCase):
+    func = functools.reduce
+
+    def test_reduce(self):
+        class Squares:
+            def __init__(self, max):
+                self.max = max
+                self.sofar = []
+
+            def __len__(self):
+                return len(self.sofar)
+
+            def __getitem__(self, i):
+                if not 0 <= i < self.max: raise IndexError
+                n = len(self.sofar)
+                while n <= i:
+                    self.sofar.append(n*n)
+                    n += 1
+                return self.sofar[i]
+    
+        self.assertEqual(self.func(lambda x, y: x+y, ['a', 'b', 'c'], ''), 'abc')
+        self.assertEqual(
+            self.func(lambda x, y: x+y, [['a', 'c'], [], ['d', 'w']], []),
+            ['a','c','d','w']
+        )
+        self.assertEqual(self.func(lambda x, y: x*y, range(2,8), 1), 5040)
+        self.assertEqual(
+            self.func(lambda x, y: x*y, range(2,21), 1L),
+            2432902008176640000L
+        )
+        self.assertEqual(self.func(lambda x, y: x+y, Squares(10)), 285)
+        self.assertEqual(self.func(lambda x, y: x+y, Squares(10), 0), 285)
+        self.assertEqual(self.func(lambda x, y: x+y, Squares(0), 0), 0)
+        self.assertRaises(TypeError, self.func)
+        self.assertRaises(TypeError, self.func, 42, 42)
+        self.assertRaises(TypeError, self.func, 42, 42, 42)
+        self.assertEqual(self.func(42, "1"), "1") # func is never called with one item
+        self.assertEqual(self.func(42, "", "1"), "1") # func is never called with one item
+        self.assertRaises(TypeError, self.func, 42, (42, 42))
+
+        class BadSeq:
+            def __getitem__(self, index):
+                raise ValueError
+        self.assertRaises(ValueError, self.func, 42, BadSeq())
+
+    # Test reduce()'s use of iterators.
+    def test_iterator_usage(self):
+        class SequenceClass:
+            def __init__(self, n):
+                self.n = n
+            def __getitem__(self, i):
+                if 0 <= i < self.n:
+                    return i
+                else:
+                    raise IndexError
+    
+        from operator import add
+        self.assertEqual(self.func(add, SequenceClass(5)), 10)
+        self.assertEqual(self.func(add, SequenceClass(5), 42), 52)
+        self.assertRaises(TypeError, self.func, add, SequenceClass(0))
+        self.assertEqual(self.func(add, SequenceClass(0), 42), 42)
+        self.assertEqual(self.func(add, SequenceClass(1)), 0)
+        self.assertEqual(self.func(add, SequenceClass(1), 42), 42)
+
+        d = {"one": 1, "two": 2, "three": 3}
+        self.assertEqual(self.func(add, d), "".join(d.keys()))
+
+    
 
 
 def test_main(verbose=None):
@@ -268,7 +336,8 @@ def test_main(verbose=None):
         TestPartialSubclass,
         TestPythonPartial,
         TestUpdateWrapper,
-        TestWraps
+        TestWraps,
+        TestReduce
     )
     test_support.run_unittest(*test_classes)
 
index d83572aa35b1a41e7a1ff577566b038376e91e95..08c54d24e88d32248c23d12f776bb50f9498c02b 100644 (file)
@@ -63,7 +63,7 @@ endif
 
 if exists("python_highlight_builtins")
   syn keyword pythonBuiltin    unichr all set abs vars int __import__ unicode
-  syn keyword pythonBuiltin    enumerate reduce exit issubclass
+  syn keyword pythonBuiltin    enumerate exit issubclass
   syn keyword pythonBuiltin    divmod file Ellipsis isinstance open any
   syn keyword pythonBuiltin    locals help filter basestring slice copyright min
   syn keyword pythonBuiltin    super sum tuple hex execfile long id chr
index 65b1400e6bb6c7af4a22cec945d3fc8781a9eecb..bab109a8ab43e001c9d9fc828f2042ed7772e1fe 100644 (file)
@@ -901,7 +901,7 @@ lambda [param_list]: returnedExpr
                 -- Creates an anonymous function. returnedExpr must be
                    an expression, not a statement (e.g., not "if xx:...",
                    "print xxx", etc.) and thus can't contain newlines.
-                   Used mostly for filter(), map(), reduce() functions, and GUI callbacks..
+                   Used mostly for filter(), map() functions, and GUI callbacks..
 List comprehensions
 result = [expression for item1 in sequence1  [if condition1]
                         [for item2 in sequence2 ... for itemN in sequenceN]
@@ -1005,11 +1005,6 @@ property()          Created a property with access controlled by functions.
 range(start [,end   Returns list of ints from >= start and < end.With 1 arg,
 [, step]])          list from 0..arg-1With 2 args, list from start..end-1With 3
                     args, list from start up to end by step
-reduce(f, list [,   Applies the binary function f to the items oflist so as to
-init])              reduce the list to a single value.If init given, it is
-                    "prepended" to list.
-                    Re-parses and re-initializes an already imported module.
-                    Useful in interactive mode, if you want to reload amodule
 reload(module)      after fixing it. If module was syntacticallycorrect but had
                     an error in initialization, mustimport it one more time
                     before calling reload().
index 1392c84d62f45179118a8f4ca919162886c4c3b3..5f50da8add538fe30139cef954abd9690c49ad5d 100644 (file)
@@ -386,7 +386,7 @@ support for features needed by `python-mode'.")
                          "isinstance" "issubclass" "iter" "len" "license"
                          "list" "locals" "long" "map" "max" "min" "object"
                          "oct" "open" "ord" "pow" "property" "range"
-                         "reduce" "reload" "repr" "round"
+                         "reload" "repr" "round"
                          "setattr" "slice" "staticmethod" "str" "sum"
                          "super" "tuple" "type" "unichr" "unicode" "vars"
                          "zip")
index 7b23210617b85bfb7336129add3b0a31ba91f4f8..1b363fcab41189a032f5c62c8a316cbb4735d01a 100644 (file)
@@ -242,12 +242,88 @@ static PyTypeObject partial_type = {
 };
 
 
+/* reduce (used to be a builtin) ********************************************/
+
+static PyObject *
+functools_reduce(PyObject *self, PyObject *args)
+{
+       PyObject *seq, *func, *result = NULL, *it;
+
+       if (!PyArg_UnpackTuple(args, "reduce", 2, 3, &func, &seq, &result))
+               return NULL;
+       if (result != NULL)
+               Py_INCREF(result);
+
+       it = PyObject_GetIter(seq);
+       if (it == NULL) {
+               PyErr_SetString(PyExc_TypeError,
+                   "reduce() arg 2 must support iteration");
+               Py_XDECREF(result);
+               return NULL;
+       }
+
+       if ((args = PyTuple_New(2)) == NULL)
+               goto Fail;
+
+       for (;;) {
+               PyObject *op2;
+
+               if (args->ob_refcnt > 1) {
+                       Py_DECREF(args);
+                       if ((args = PyTuple_New(2)) == NULL)
+                               goto Fail;
+               }
+
+               op2 = PyIter_Next(it);
+               if (op2 == NULL) {
+                       if (PyErr_Occurred())
+                               goto Fail;
+                       break;
+               }
+
+               if (result == NULL)
+                       result = op2;
+               else {
+                       PyTuple_SetItem(args, 0, result);
+                       PyTuple_SetItem(args, 1, op2);
+                       if ((result = PyEval_CallObject(func, args)) == NULL)
+                               goto Fail;
+               }
+       }
+
+       Py_DECREF(args);
+
+       if (result == NULL)
+               PyErr_SetString(PyExc_TypeError,
+                          "reduce() of empty sequence with no initial value");
+
+       Py_DECREF(it);
+       return result;
+
+Fail:
+       Py_XDECREF(args);
+       Py_XDECREF(result);
+       Py_DECREF(it);
+       return NULL;
+}
+
+PyDoc_STRVAR(functools_reduce_doc,
+"reduce(function, sequence[, initial]) -> value\n\
+\n\
+Apply a function of two arguments cumulatively to the items of a sequence,\n\
+from left to right, so as to reduce the sequence to a single value.\n\
+For example, reduce(lambda x, y: x+y, [1, 2, 3, 4, 5]) calculates\n\
+((((1+2)+3)+4)+5).  If initial is present, it is placed before the items\n\
+of the sequence in the calculation, and serves as a default when the\n\
+sequence is empty.");
+
 /* module level code ********************************************************/
 
 PyDoc_STRVAR(module_doc,
 "Tools that operate on functions.");
 
 static PyMethodDef module_methods[] = {
+       {"reduce",      functools_reduce,     METH_VARARGS, functools_reduce_doc},
        {NULL,          NULL}           /* sentinel */
 };
 
index 11f42b894c86911114443f5ae57d59051d1fcfc8..68523e4904bca215d6e70d9dc1abefc1da2c5652 100644 (file)
@@ -3012,7 +3012,7 @@ init_ast(void)
         if (PyDict_SetItemString(d, "AST", (PyObject*)AST_type) < 0) return;
         if (PyModule_AddIntConstant(m, "PyCF_ONLY_AST", PyCF_ONLY_AST) < 0)
                 return;
-        if (PyModule_AddStringConstant(m, "__version__", "45597") < 0)
+        if (PyModule_AddStringConstant(m, "__version__", "51600") < 0)
                 return;
         if (PyDict_SetItemString(d, "mod", (PyObject*)mod_type) < 0) return;
         if (PyDict_SetItemString(d, "Module", (PyObject*)Module_type) < 0)