SF patch #941881:  PEP 309 Implementation (Partial Function Application).

Combined efforts of many including Peter Harris, Hye-Shik Chang,
Martin v. Löwis, Nick Coghlan, Paul Moore, and Raymond Hettinger.
diff --git a/Doc/lib/lib.tex b/Doc/lib/lib.tex
index 37ab91d..9913449 100644
--- a/Doc/lib/lib.tex
+++ b/Doc/lib/lib.tex
@@ -132,6 +132,7 @@
 \input{libarray}
 \input{libsets}
 \input{libitertools}
+\input{libfunctional}
 \input{libcfgparser}
 \input{libfileinput}
 \input{libcalendar}
diff --git a/Doc/lib/libfunctional.tex b/Doc/lib/libfunctional.tex
new file mode 100644
index 0000000..c092d6d
--- /dev/null
+++ b/Doc/lib/libfunctional.tex
@@ -0,0 +1,72 @@
+\section{\module{functional} ---
+         Higher order functions and operations on callable objects.}
+
+\declaremodule{standard}{functional}		% standard library, in Python
+
+\moduleauthor{Peter Harris}{scav@blueyonder.co.uk}
+\moduleauthor{Raymond Hettinger}{python@rcn.com}
+\sectionauthor{Peter Harris}{scav@blueyonder.co.uk}
+
+\modulesynopsis{Higher-order functions and operations on callable objects.}
+
+
+The \module{functional} module is for higher-order functions: functions
+that act on or return other functions. In general, any callable object can
+be treated as a function for the purposes of this module.
+
+
+The \module{functional} module defines the following function:
+
+\begin{funcdesc}{partial}{func\optional{,*args}\optional{, **keywords}}
+Return a new \class{partial} object which when called will behave like
+\var{func} called with the positional arguments \var{args} and keyword
+arguments \var{keywords}. If more arguments are supplied to the call, they
+are appended to \var{args}. If additional keyword arguments are supplied,
+they extend and override \var{keywords}. Roughly equivalent to:
+  \begin{verbatim}
+        def partial(func, *args, **keywords):
+            def newfunc(*fargs, **fkeywords):
+                newkeywords = keywords.copy()
+                newkeywords.update(fkeywords)
+                return func(*(args + fargs), **newkeywords)
+            newfunc.func = func
+            newfunc.args = args
+            newfunc.keywords = keywords
+            return newfunc
+  \end{verbatim}
+
+The \function{partial} is used for partial function application which
+``freezes'' some portion of a function's arguments and/or keywords
+resulting in an new object with a simplified signature.  For example,
+\function{partial} can be used to create a callable that behaves like
+the \function{int} function where the \var{base} argument defaults to
+two:
+  \begin{verbatim}
+        >>> basetwo = partial(int, base=2)
+        >>> basetwo('10010')
+        18
+  \end{verbatim}
+\end{funcdesc}
+
+
+
+\subsection{\class{partial} Objects \label{partial-objects}}
+
+
+\class{partial} objects are callable objects created by \function{partial()}.
+They have three read-only attributes:
+
+\begin{memberdesc}[callable]{func}{}
+A callable object or function.  Calls to the \class{partial} object will
+be forwarded to \member{func} with new arguments and keywords.
+\end{memberdesc}
+
+\begin{memberdesc}[tuple]{args}{}
+The leftmost positional arguments that will be prepended to the
+positional arguments provided to a \class{partial} object call.
+\end{memberdesc}
+
+\begin{memberdesc}[dict]{keywords}{}
+The keyword arguments that will be supplied when the \class{partial} object
+is called.
+\end{memberdesc}
diff --git a/Lib/test/test_functional.py b/Lib/test/test_functional.py
new file mode 100644
index 0000000..db3a289
--- /dev/null
+++ b/Lib/test/test_functional.py
@@ -0,0 +1,154 @@
+import functional
+import unittest
+from test import test_support
+
+@staticmethod
+def PythonPartial(func, *args, **keywords):
+    'Pure Python approximation of partial()'
+    def newfunc(*fargs, **fkeywords):
+        newkeywords = keywords.copy()
+        newkeywords.update(fkeywords)
+        return func(*(args + fargs), **newkeywords)
+    newfunc.func = func
+    newfunc.args = args
+    newfunc.keywords = keywords
+    return newfunc
+
+def capture(*args, **kw):
+    """capture all positional and keyword arguments"""
+    return args, kw
+
+class TestPartial(unittest.TestCase):
+
+    thetype = functional.partial
+
+    def test_basic_examples(self):
+        p = self.thetype(capture, 1, 2, a=10, b=20)
+        self.assertEqual(p(3, 4, b=30, c=40),
+                         ((1, 2, 3, 4), dict(a=10, b=30, c=40)))
+        p = self.thetype(map, lambda x: x*10)
+        self.assertEqual(p([1,2,3,4]), [10, 20, 30, 40])
+
+    def test_attributes(self):
+        p = self.thetype(capture, 1, 2, a=10, b=20)
+        # attributes should be readable
+        self.assertEqual(p.func, capture)
+        self.assertEqual(p.args, (1, 2))
+        self.assertEqual(p.keywords, dict(a=10, b=20))
+        # attributes should not be writable
+        if not isinstance(self.thetype, type):
+            return
+        self.assertRaises(TypeError, setattr, p, 'func', map)
+        self.assertRaises(TypeError, setattr, p, 'args', (1, 2))
+        self.assertRaises(TypeError, setattr, p, 'keywords', dict(a=1, b=2))
+
+    def test_argument_checking(self):
+        self.assertRaises(TypeError, self.thetype)     # need at least a func arg
+        try:
+            self.thetype(2)()
+        except TypeError:
+            pass
+        else:
+            self.fail('First arg not checked for callability')
+
+    def test_protection_of_callers_dict_argument(self):
+        # a caller's dictionary should not be altered by partial
+        def func(a=10, b=20):
+            return a
+        d = {'a':3}
+        p = self.thetype(func, a=5)
+        self.assertEqual(p(**d), 3)
+        self.assertEqual(d, {'a':3})
+        p(b=7)
+        self.assertEqual(d, {'a':3})
+
+    def test_arg_combinations(self):
+        # exercise special code paths for zero args in either partial
+        # object or the caller
+        p = self.thetype(capture)
+        self.assertEqual(p(), ((), {}))
+        self.assertEqual(p(1,2), ((1,2), {}))
+        p = self.thetype(capture, 1, 2)
+        self.assertEqual(p(), ((1,2), {}))
+        self.assertEqual(p(3,4), ((1,2,3,4), {}))
+
+    def test_kw_combinations(self):
+        # exercise special code paths for no keyword args in
+        # either the partial object or the caller
+        p = self.thetype(capture)
+        self.assertEqual(p(), ((), {}))
+        self.assertEqual(p(a=1), ((), {'a':1}))
+        p = self.thetype(capture, a=1)
+        self.assertEqual(p(), ((), {'a':1}))
+        self.assertEqual(p(b=2), ((), {'a':1, 'b':2}))
+        # keyword args in the call override those in the partial object
+        self.assertEqual(p(a=3, b=2), ((), {'a':3, 'b':2}))
+
+    def test_positional(self):
+        # make sure positional arguments are captured correctly
+        for args in [(), (0,), (0,1), (0,1,2), (0,1,2,3)]:
+            p = self.thetype(capture, *args)
+            expected = args + ('x',)
+            got, empty = p('x')
+            self.failUnless(expected == got and empty == {})
+
+    def test_keyword(self):
+        # make sure keyword arguments are captured correctly
+        for a in ['a', 0, None, 3.5]:
+            p = self.thetype(capture, a=a)
+            expected = {'a':a,'x':None}
+            empty, got = p(x=None)
+            self.failUnless(expected == got and empty == ())
+
+    def test_no_side_effects(self):
+        # make sure there are no side effects that affect subsequent calls
+        p = self.thetype(capture, 0, a=1)
+        args1, kw1 = p(1, b=2)
+        self.failUnless(args1 == (0,1) and kw1 == {'a':1,'b':2})
+        args2, kw2 = p()
+        self.failUnless(args2 == (0,) and kw2 == {'a':1})
+
+    def test_error_propagation(self):
+        def f(x, y):
+            x / y
+        self.assertRaises(ZeroDivisionError, self.thetype(f, 1, 0))
+        self.assertRaises(ZeroDivisionError, self.thetype(f, 1), 0)
+        self.assertRaises(ZeroDivisionError, self.thetype(f), 1, 0)
+        self.assertRaises(ZeroDivisionError, self.thetype(f, y=0), 1)
+
+
+class PartialSubclass(functional.partial):
+    pass
+
+class TestPartialSubclass(TestPartial):
+
+    thetype = PartialSubclass
+
+
+class TestPythonPartial(TestPartial):
+
+    thetype = PythonPartial
+
+
+
+def test_main(verbose=None):
+    import sys
+    test_classes = (
+        TestPartial,
+        TestPartialSubclass,
+        TestPythonPartial,
+    )
+    test_support.run_unittest(*test_classes)
+
+    # verify reference counting
+    if verbose and hasattr(sys, "gettotalrefcount"):
+        import gc
+        counts = [None] * 5
+        for i in xrange(len(counts)):
+            test_support.run_unittest(*test_classes)
+            gc.collect()
+            counts[i] = sys.gettotalrefcount()
+        print counts
+
+if __name__ == '__main__':
+    test_main(verbose=True)
diff --git a/Misc/NEWS b/Misc/NEWS
index 68762a6..4add264 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -34,6 +34,8 @@
 Extension Modules
 -----------------
 
+- Added functional.partial().  See PEP309.
+
 - Patch #1093585: raise a ValueError for negative history items in readline.
   {remove_history,replace_history}
 
diff --git a/Modules/functionalmodule.c b/Modules/functionalmodule.c
new file mode 100644
index 0000000..18efab2
--- /dev/null
+++ b/Modules/functionalmodule.c
@@ -0,0 +1,225 @@
+
+#include "Python.h"
+#include "structmember.h"
+
+/* Functional module written and maintained 
+   by Hye-Shik Chang <perky@FreeBSD.org>
+   with adaptations by Raymond Hettinger <python@rcn.com>
+   Copyright (c) 2004, 2005 Python Software Foundation.
+   All rights reserved.
+*/
+
+/* partial object **********************************************************/
+
+typedef struct {
+	PyObject_HEAD
+	PyObject *fn;
+	PyObject *args;
+	PyObject *kw;
+} partialobject;
+
+static PyTypeObject partial_type;
+
+static PyObject *
+partial_new(PyTypeObject *type, PyObject *args, PyObject *kw)
+{
+	PyObject *func;
+	partialobject *pto;
+
+	if (PyTuple_GET_SIZE(args) < 1) {
+		PyErr_SetString(PyExc_TypeError,
+				"type 'partial' takes at least one argument");
+		return NULL;
+	}
+
+	func = PyTuple_GET_ITEM(args, 0);
+	if (!PyCallable_Check(func)) {
+		PyErr_SetString(PyExc_TypeError,
+				"the first argument must be callable");
+		return NULL;
+	}
+
+	/* create partialobject structure */
+	pto = (partialobject *)type->tp_alloc(type, 0);
+	if (pto == NULL)
+		return NULL;
+
+	pto->fn = func;
+	Py_INCREF(func);
+	pto->args = PyTuple_GetSlice(args, 1, INT_MAX);
+	if (pto->args == NULL) {
+		pto->kw = NULL;
+		Py_DECREF(pto);
+		return NULL;
+	}
+	if (kw != NULL) {
+		pto->kw = PyDict_Copy(kw);
+		if (pto->kw == NULL) {
+			Py_DECREF(pto);
+			return NULL;
+		}
+	} else {
+		pto->kw = Py_None;
+		Py_INCREF(Py_None);
+	}
+
+	return (PyObject *)pto;
+}
+
+static void
+partial_dealloc(partialobject *pto)
+{
+	PyObject_GC_UnTrack(pto);
+	Py_XDECREF(pto->fn);
+	Py_XDECREF(pto->args);
+	Py_XDECREF(pto->kw);
+	pto->ob_type->tp_free(pto);
+}
+
+static PyObject *
+partial_call(partialobject *pto, PyObject *args, PyObject *kw)
+{
+	PyObject *ret;
+	PyObject *argappl = NULL, *kwappl = NULL;
+
+	assert (PyCallable_Check(pto->fn));
+	assert (PyTuple_Check(pto->args));
+	assert (pto->kw == Py_None  ||  PyDict_Check(pto->kw));
+
+	if (PyTuple_GET_SIZE(pto->args) == 0) {
+		argappl = args;
+		Py_INCREF(args);
+	} else if (PyTuple_GET_SIZE(args) == 0) {
+		argappl = pto->args;
+		Py_INCREF(pto->args);
+	} else {
+		argappl = PySequence_Concat(pto->args, args);
+		if (argappl == NULL)
+			return NULL;
+	}
+
+	if (pto->kw == Py_None) {
+		kwappl = kw;
+		Py_XINCREF(kw);
+	} else {
+		kwappl = PyDict_Copy(pto->kw);
+		if (kwappl == NULL) {
+			Py_DECREF(argappl);
+			return NULL;
+		}
+		if (kw != NULL) {
+			if (PyDict_Merge(kwappl, kw, 1) != 0) {
+				Py_DECREF(argappl);
+				Py_DECREF(kwappl);
+				return NULL;
+			}
+		}
+	}
+
+	ret = PyObject_Call(pto->fn, argappl, kwappl);
+	Py_DECREF(argappl);
+	Py_XDECREF(kwappl);
+	return ret;
+}
+
+static int
+partial_traverse(partialobject *pto, visitproc visit, void *arg)
+{
+	Py_VISIT(pto->fn);
+	Py_VISIT(pto->args);
+	Py_VISIT(pto->kw);
+	return 0;
+}
+
+PyDoc_STRVAR(partial_doc,
+"partial(func, *args, **keywords) - new function with partial application\n\
+	of the given arguments and keywords.\n");
+
+#define OFF(x) offsetof(partialobject, x)
+static PyMemberDef partial_memberlist[] = {
+	{"func",	T_OBJECT,	OFF(fn),	READONLY,
+	 "function object to use in future partial calls"},
+	{"args",	T_OBJECT,	OFF(args),	READONLY,
+	 "tuple of arguments to future partial calls"},
+	{"keywords",	T_OBJECT,	OFF(kw),	READONLY,
+	 "dictionary of keyword arguments to future partial calls"},
+	{NULL}  /* Sentinel */
+};
+
+static PyTypeObject partial_type = {
+	PyObject_HEAD_INIT(NULL)
+	0,				/* ob_size */
+	"functional.partial",		/* tp_name */
+	sizeof(partialobject),		/* tp_basicsize */
+	0,				/* tp_itemsize */
+	/* methods */
+	(destructor)partial_dealloc,	/* tp_dealloc */
+	0,				/* tp_print */
+	0,				/* tp_getattr */
+	0,				/* tp_setattr */
+	0,				/* tp_compare */
+	0,				/* tp_repr */
+	0,				/* tp_as_number */
+	0,				/* tp_as_sequence */
+	0,				/* tp_as_mapping */
+	0,				/* tp_hash */
+	(ternaryfunc)partial_call,	/* tp_call */
+	0,				/* tp_str */
+	PyObject_GenericGetAttr,	/* tp_getattro */
+	0,				/* tp_setattro */
+	0,				/* tp_as_buffer */
+	Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
+		Py_TPFLAGS_BASETYPE,	/* tp_flags */
+	partial_doc,			/* tp_doc */
+	(traverseproc)partial_traverse,	/* tp_traverse */
+	0,				/* tp_clear */
+	0,				/* tp_richcompare */
+	0,				/* tp_weaklistoffset */
+	0,				/* tp_iter */
+	0,				/* tp_iternext */
+	0,				/* tp_methods */
+	partial_memberlist,		/* tp_members */
+	0,				/* tp_getset */
+	0,				/* tp_base */
+	0,				/* tp_dict */
+	0,				/* tp_descr_get */
+	0,				/* tp_descr_set */
+	0,				/* tp_dictoffset */
+	0,				/* tp_init */
+	0,				/* tp_alloc */
+	partial_new,			/* tp_new */
+	PyObject_GC_Del,		/* tp_free */
+};
+
+
+/* module level code ********************************************************/
+
+PyDoc_STRVAR(module_doc,
+"Tools for functional programming.");
+
+static PyMethodDef module_methods[] = {
+ 	{NULL,		NULL}		/* sentinel */
+};
+
+PyMODINIT_FUNC
+initfunctional(void)
+{
+	int i;
+	PyObject *m;
+	char *name;
+	PyTypeObject *typelist[] = {
+		&partial_type,
+		NULL
+	};
+
+	m = Py_InitModule3("functional", module_methods, module_doc);
+
+	for (i=0 ; typelist[i] != NULL ; i++) {
+		if (PyType_Ready(typelist[i]) < 0)
+			return;
+		name = strchr(typelist[i]->tp_name, '.');
+		assert (name != NULL);
+		Py_INCREF(typelist[i]);
+		PyModule_AddObject(m, name+1, (PyObject *)typelist[i]);
+	}
+}
diff --git a/PC/VC6/pythoncore.dsp b/PC/VC6/pythoncore.dsp
index 357ad49..8fbb998 100644
--- a/PC/VC6/pythoncore.dsp
+++ b/PC/VC6/pythoncore.dsp
@@ -301,6 +301,10 @@
 # End Source File

 # Begin Source File

 

+SOURCE=..\..\Modules\functionalmodule.c

+# End Source File

+# Begin Source File

+

 SOURCE=..\..\Python\future.c

 # End Source File

 # Begin Source File

diff --git a/PC/config.c b/PC/config.c
index 983255a..bd040b0 100644
--- a/PC/config.c
+++ b/PC/config.c
@@ -53,6 +53,7 @@
 extern void initparser(void);
 extern void init_winreg(void);
 extern void initdatetime(void);
+extern void initfunctional(void);
 
 extern void init_multibytecodec(void);
 extern void init_codecs_cn(void);
@@ -124,6 +125,7 @@
 	{"parser", initparser},
 	{"_winreg", init_winreg},
 	{"datetime", initdatetime},
+	{"functional", initfunctional},
 
 	{"xxsubtype", initxxsubtype},
 	{"zipimport", initzipimport},
diff --git a/PCbuild/pythoncore.vcproj b/PCbuild/pythoncore.vcproj
index 1bf6704..3d8aaf2 100644
--- a/PCbuild/pythoncore.vcproj
+++ b/PCbuild/pythoncore.vcproj
@@ -1294,6 +1294,33 @@
 			</FileConfiguration>
 		</File>
 		<File
+			RelativePath="..\Modules\functionalmodule.c">
+			<FileConfiguration
+				Name="Release|Win32">
+				<Tool
+					Name="VCCLCompilerTool"
+					Optimization="2"
+					AdditionalIncludeDirectories=""
+					PreprocessorDefinitions="NDEBUG;WIN32;_WINDOWS;USE_DL_EXPORT;$(NoInherit)"/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="Debug|Win32">
+				<Tool
+					Name="VCCLCompilerTool"
+					Optimization="0"
+					AdditionalIncludeDirectories=""
+					PreprocessorDefinitions="_DEBUG;USE_DL_EXPORT;WIN32;_WINDOWS;$(NoInherit)"/>
+			</FileConfiguration>
+			<FileConfiguration
+				Name="ReleaseItanium|Win32">
+				<Tool
+					Name="VCCLCompilerTool"
+					Optimization="2"
+					AdditionalIncludeDirectories=""
+					PreprocessorDefinitions="NDEBUG;WIN32;_WINDOWS;USE_DL_EXPORT;$(NoInherit)"/>
+			</FileConfiguration>
+		</File>
+		<File
 			RelativePath="..\Python\future.c">
 			<FileConfiguration
 				Name="Release|Win32">
diff --git a/setup.py b/setup.py
index 9b41f4c..85322a8 100644
--- a/setup.py
+++ b/setup.py
@@ -355,6 +355,8 @@
         exts.append( Extension("_heapq", ["_heapqmodule.c"]) )
         # operator.add() and similar goodies
         exts.append( Extension('operator', ['operator.c']) )
+        # functional
+        exts.append( Extension("functional", ["functionalmodule.c"]) )
         # Python C API test module
         exts.append( Extension('_testcapi', ['_testcapimodule.c']) )
         # static Unicode character database