From c649ec5b69bd864914e02a2a9ec73c23bd307448 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Mon, 29 May 2006 12:43:05 +0000 Subject: Apply modified version of Collin Winter's patch #1478788 Renames functional extension module to _functools and adds a Python functools module so that utility functions like update_wrapper can be added easily. --- Doc/Makefile.deps | 1 + Doc/lib/lib.tex | 4 +- Doc/lib/libfunctional.tex | 81 ------------- Doc/lib/libfunctools.tex | 81 +++++++++++++ Lib/test/test_functional.py | 177 ---------------------------- Lib/test/test_functools.py | 177 ++++++++++++++++++++++++++++ Misc/NEWS | 12 +- Modules/_functoolsmodule.c | 277 ++++++++++++++++++++++++++++++++++++++++++++ Modules/functionalmodule.c | 277 -------------------------------------------- PC/VC6/pythoncore.dsp | 2 +- PC/config.c | 4 +- PCbuild/pythoncore.vcproj | 5 +- setup.py | 4 +- 13 files changed, 554 insertions(+), 548 deletions(-) delete mode 100644 Doc/lib/libfunctional.tex create mode 100644 Doc/lib/libfunctools.tex delete mode 100644 Lib/test/test_functional.py create mode 100644 Lib/test/test_functools.py create mode 100644 Modules/_functoolsmodule.c delete mode 100644 Modules/functionalmodule.c diff --git a/Doc/Makefile.deps b/Doc/Makefile.deps index 11c6de0..2fc3250 100644 --- a/Doc/Makefile.deps +++ b/Doc/Makefile.deps @@ -262,6 +262,7 @@ LIBFILES= $(MANSTYLES) $(INDEXSTYLES) $(COMMONTEX) \ lib/libsimplexmlrpc.tex \ lib/libdocxmlrpc.tex \ lib/libpyexpat.tex \ + lib/libfunctools.tex \ lib/xmldom.tex \ lib/xmldomminidom.tex \ lib/xmldompulldom.tex \ diff --git a/Doc/lib/lib.tex b/Doc/lib/lib.tex index cf657c3..0691179 100644 --- a/Doc/lib/lib.tex +++ b/Doc/lib/lib.tex @@ -129,8 +129,8 @@ and how to embed it in other applications. % Functions, Functional, Generators and Iterators % XXX intro functional \input{libitertools} -\input{libfunctional} -\input{liboperator} % from runtime - better with itertools and functional +\input{libfunctools} +\input{liboperator} % from runtime - better with itertools and functools % ============= diff --git a/Doc/lib/libfunctional.tex b/Doc/lib/libfunctional.tex deleted file mode 100644 index 9218454..0000000 --- a/Doc/lib/libfunctional.tex +++ /dev/null @@ -1,81 +0,0 @@ -\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.} - -\versionadded{2.5} - -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 a 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.__doc__ = 'Convert base 2 string to an int.' - >>> 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} - -\class{partial} objects are like \class{function} objects in that they are -callable, weak referencable, and can have attributes. There are some -important differences. For instance, the \member{__name__} and -\member{__doc__} attributes are not created automatically. Also, -\class{partial} objects defined in classes behave like static methods and -do not transform into bound methods during instance attribute look-up. diff --git a/Doc/lib/libfunctools.tex b/Doc/lib/libfunctools.tex new file mode 100644 index 0000000..a25a23a --- /dev/null +++ b/Doc/lib/libfunctools.tex @@ -0,0 +1,81 @@ +\section{\module{functools} --- + Higher order functions and operations on callable objects.} + +\declaremodule{standard}{functools} % 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.} + +\versionadded{2.5} + +The \module{functools} 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{functools} 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 a 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.__doc__ = 'Convert base 2 string to an int.' + >>> 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} + +\class{partial} objects are like \class{function} objects in that they are +callable, weak referencable, and can have attributes. There are some +important differences. For instance, the \member{__name__} and +\member{__doc__} attributes are not created automatically. Also, +\class{partial} objects defined in classes behave like static methods and +do not transform into bound methods during instance attribute look-up. diff --git a/Lib/test/test_functional.py b/Lib/test/test_functional.py deleted file mode 100644 index 5078a2e..0000000 --- a/Lib/test/test_functional.py +++ /dev/null @@ -1,177 +0,0 @@ -import functional -import unittest -from test import test_support -from weakref import proxy - -@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) - - def test_attributes(self): - p = self.thetype(hex) - try: - del p.__dict__ - except TypeError: - pass - else: - self.fail('partial object allowed __dict__ to be deleted') - - def test_weakref(self): - f = self.thetype(int, base=16) - p = proxy(f) - self.assertEqual(f.func, p.func) - f = None - self.assertRaises(ReferenceError, getattr, p, 'func') - - def test_with_bound_and_unbound_methods(self): - data = map(str, range(10)) - join = self.thetype(str.join, '') - self.assertEqual(join(data), '0123456789') - join = self.thetype(''.join) - self.assertEqual(join(data), '0123456789') - -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/Lib/test/test_functools.py b/Lib/test/test_functools.py new file mode 100644 index 0000000..609e8f4 --- /dev/null +++ b/Lib/test/test_functools.py @@ -0,0 +1,177 @@ +import functools +import unittest +from test import test_support +from weakref import proxy + +@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 = functools.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) + + def test_attributes(self): + p = self.thetype(hex) + try: + del p.__dict__ + except TypeError: + pass + else: + self.fail('partial object allowed __dict__ to be deleted') + + def test_weakref(self): + f = self.thetype(int, base=16) + p = proxy(f) + self.assertEqual(f.func, p.func) + f = None + self.assertRaises(ReferenceError, getattr, p, 'func') + + def test_with_bound_and_unbound_methods(self): + data = map(str, range(10)) + join = self.thetype(str.join, '') + self.assertEqual(join(data), '0123456789') + join = self.thetype(''.join) + self.assertEqual(join(data), '0123456789') + +class PartialSubclass(functools.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 90407d6..24eb835 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -4,10 +4,10 @@ Python News (editors: check NEWS.help for information about editing NEWS using ReST.) -What's New in Python 2.5 alpha 3? +What's New in Python 2.5 beta 1? ================================= -*Release date: XX-MAY-2006* +*Release date: XX-JUN-2006* Core and builtins ----------------- @@ -62,6 +62,11 @@ Core and builtins Extension Modules ----------------- +- Patch #1478788 (modified version): The functional extension module has + been renamed to _functools and a functools Python wrapper module added. + This provides a home for additional function related utilities that are + not specifically about functional programming. See PEP 309. + - Patch #1493701: performance enhancements for struct module. - Patch #1490224: time.altzone is now set correctly on Cygwin. @@ -82,6 +87,7 @@ Extension Modules - Calling Tk_Init twice is refused if the first call failed as that may deadlock. + Library ------- @@ -142,6 +148,8 @@ Tools Documentation ------------- + + What's New in Python 2.5 alpha 2? ================================= diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c new file mode 100644 index 0000000..54abb89 --- /dev/null +++ b/Modules/_functoolsmodule.c @@ -0,0 +1,277 @@ + +#include "Python.h" +#include "structmember.h" + +/* _functools module written and maintained + by Hye-Shik Chang + with adaptations by Raymond Hettinger + Copyright (c) 2004, 2005, 2006 Python Software Foundation. + All rights reserved. +*/ + +/* partial object **********************************************************/ + +typedef struct { + PyObject_HEAD + PyObject *fn; + PyObject *args; + PyObject *kw; + PyObject *dict; + PyObject *weakreflist; /* List of weak references */ +} 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, PY_SSIZE_T_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); + } + + pto->weakreflist = NULL; + pto->dict = NULL; + + return (PyObject *)pto; +} + +static void +partial_dealloc(partialobject *pto) +{ + PyObject_GC_UnTrack(pto); + if (pto->weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *) pto); + Py_XDECREF(pto->fn); + Py_XDECREF(pto->args); + Py_XDECREF(pto->kw); + Py_XDECREF(pto->dict); + 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); + Py_VISIT(pto->dict); + 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 PyObject * +partial_get_dict(partialobject *pto) +{ + if (pto->dict == NULL) { + pto->dict = PyDict_New(); + if (pto->dict == NULL) + return NULL; + } + Py_INCREF(pto->dict); + return pto->dict; +} + +static int +partial_set_dict(partialobject *pto, PyObject *value) +{ + PyObject *tmp; + + /* It is illegal to del p.__dict__ */ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, + "a partial object's dictionary may not be deleted"); + return -1; + } + /* Can only set __dict__ to a dictionary */ + if (!PyDict_Check(value)) { + PyErr_SetString(PyExc_TypeError, + "setting partial object's dictionary to a non-dict"); + return -1; + } + tmp = pto->dict; + Py_INCREF(value); + pto->dict = value; + Py_XDECREF(tmp); + return 0; +} + +static PyGetSetDef partial_getsetlist[] = { + {"__dict__", (getter)partial_get_dict, (setter)partial_set_dict}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject partial_type = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "functools.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 */ + PyObject_GenericSetAttr, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_WEAKREFS, /* tp_flags */ + partial_doc, /* tp_doc */ + (traverseproc)partial_traverse, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + offsetof(partialobject, weakreflist), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + partial_memberlist, /* tp_members */ + partial_getsetlist, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + offsetof(partialobject, dict), /* 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 that operate on functions."); + +static PyMethodDef module_methods[] = { + {NULL, NULL} /* sentinel */ +}; + +PyMODINIT_FUNC +init_functools(void) +{ + int i; + PyObject *m; + char *name; + PyTypeObject *typelist[] = { + &partial_type, + NULL + }; + + m = Py_InitModule3("_functools", module_methods, module_doc); + if (m == NULL) + return; + + 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/Modules/functionalmodule.c b/Modules/functionalmodule.c deleted file mode 100644 index 38ef43a..0000000 --- a/Modules/functionalmodule.c +++ /dev/null @@ -1,277 +0,0 @@ - -#include "Python.h" -#include "structmember.h" - -/* Functional module written and maintained - by Hye-Shik Chang - with adaptations by Raymond Hettinger - Copyright (c) 2004, 2005 Python Software Foundation. - All rights reserved. -*/ - -/* partial object **********************************************************/ - -typedef struct { - PyObject_HEAD - PyObject *fn; - PyObject *args; - PyObject *kw; - PyObject *dict; - PyObject *weakreflist; /* List of weak references */ -} 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, PY_SSIZE_T_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); - } - - pto->weakreflist = NULL; - pto->dict = NULL; - - return (PyObject *)pto; -} - -static void -partial_dealloc(partialobject *pto) -{ - PyObject_GC_UnTrack(pto); - if (pto->weakreflist != NULL) - PyObject_ClearWeakRefs((PyObject *) pto); - Py_XDECREF(pto->fn); - Py_XDECREF(pto->args); - Py_XDECREF(pto->kw); - Py_XDECREF(pto->dict); - 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); - Py_VISIT(pto->dict); - 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 PyObject * -partial_get_dict(partialobject *pto) -{ - if (pto->dict == NULL) { - pto->dict = PyDict_New(); - if (pto->dict == NULL) - return NULL; - } - Py_INCREF(pto->dict); - return pto->dict; -} - -static int -partial_set_dict(partialobject *pto, PyObject *value) -{ - PyObject *tmp; - - /* It is illegal to del p.__dict__ */ - if (value == NULL) { - PyErr_SetString(PyExc_TypeError, - "a partial object's dictionary may not be deleted"); - return -1; - } - /* Can only set __dict__ to a dictionary */ - if (!PyDict_Check(value)) { - PyErr_SetString(PyExc_TypeError, - "setting partial object's dictionary to a non-dict"); - return -1; - } - tmp = pto->dict; - Py_INCREF(value); - pto->dict = value; - Py_XDECREF(tmp); - return 0; -} - -static PyGetSetDef partial_getsetlist[] = { - {"__dict__", (getter)partial_get_dict, (setter)partial_set_dict}, - {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 */ - PyObject_GenericSetAttr, /* tp_setattro */ - 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | - Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_WEAKREFS, /* tp_flags */ - partial_doc, /* tp_doc */ - (traverseproc)partial_traverse, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - offsetof(partialobject, weakreflist), /* tp_weaklistoffset */ - 0, /* tp_iter */ - 0, /* tp_iternext */ - 0, /* tp_methods */ - partial_memberlist, /* tp_members */ - partial_getsetlist, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - offsetof(partialobject, dict), /* 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); - if (m == NULL) - return; - - 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 cf3200c..ec2ec3e 100644 --- a/PC/VC6/pythoncore.dsp +++ b/PC/VC6/pythoncore.dsp @@ -313,7 +313,7 @@ SOURCE=..\..\Objects\funcobject.c # End Source File # Begin Source File -SOURCE=..\..\Modules\functionalmodule.c +SOURCE=..\..\Modules\_functoolsmodule.c # End Source File # Begin Source File diff --git a/PC/config.c b/PC/config.c index be11d3f..b832d7c 100644 --- a/PC/config.c +++ b/PC/config.c @@ -54,7 +54,7 @@ extern void initparser(void); extern void init_winreg(void); extern void init_struct(void); extern void initdatetime(void); -extern void initfunctional(void); +extern void init_functools(void); extern void initzlib(void); extern void init_multibytecodec(void); @@ -132,7 +132,7 @@ struct _inittab _PyImport_Inittab[] = { {"_winreg", init_winreg}, {"_struct", init_struct}, {"datetime", initdatetime}, - {"functional", initfunctional}, + {"_functools", init_functools}, {"xxsubtype", initxxsubtype}, {"zipimport", initzipimport}, diff --git a/PCbuild/pythoncore.vcproj b/PCbuild/pythoncore.vcproj index c37e9b9..bb9f7af 100644 --- a/PCbuild/pythoncore.vcproj +++ b/PCbuild/pythoncore.vcproj @@ -515,10 +515,7 @@ RelativePath="..\Objects\funcobject.c"> - - + RelativePath="..\Modules\_functoolsmodule.c"> diff --git a/setup.py b/setup.py index 0abafd9..9b2fac4 100644 --- a/setup.py +++ b/setup.py @@ -377,8 +377,8 @@ class PyBuildExt(build_ext): exts.append( Extension("_heapq", ["_heapqmodule.c"]) ) # operator.add() and similar goodies exts.append( Extension('operator', ['operator.c']) ) - # functional - exts.append( Extension("functional", ["functionalmodule.c"]) ) + # _functools + exts.append( Extension("_functools", ["_functoolsmodule.c"]) ) # Python C API test module exts.append( Extension('_testcapi', ['_testcapimodule.c']) ) # profilers (_lsprof is for cProfile.py) -- cgit v0.12