Index: Lib/test/test_functools.py =================================================================== --- Lib/test/test_functools.py (revision 82374) +++ Lib/test/test_functools.py (working copy) @@ -8,6 +8,14 @@ @staticmethod def PythonPartial(func, *args, **keywords): 'Pure Python approximation of partial()' + if hasattr(func, 'func'): + args = func.args + args + tmpkw = func.keywords.copy() + tmpkw.update(keywords) + keywords = tmpkw + del tmpkw + func = func.func + def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) @@ -151,6 +159,25 @@ f_copy = pickle.loads(pickle.dumps(f)) self.assertEqual(signature(f), signature(f_copy)) + def test_nested(self): + partial = self.thetype + inner = partial(signature, 'asdf') + nested = partial(inner, bar=True) + flat = partial(signature, 'asdf', bar=True) + self.assertEqual(signature(nested), signature(flat)) + + def test_nested_with_state(self): + partial = self.thetype + inner = partial(signature, 'asdf') + # Now add some state to inner: + inner.something = True + nested = partial(inner, bar=True) + self.assertIs(inner, nested.func) + # Now delete the state + del inner.something + nested = partial(inner, bar=True) + self.assertIs(inner, nested.func) + class PartialSubclass(functools.partial): pass @@ -158,13 +185,24 @@ thetype = PartialSubclass + def test_nested(self): + partial = self.thetype + f = partial(signature, 'asdf') + g = partial(f, bar=True) + self.assertEqual(signature(g), (f, (), {'bar': True}, {})) + class TestPythonPartial(TestPartial): thetype = PythonPartial # the python version isn't picklable - def test_pickle(self): pass + def test_pickle(self): + pass + # the python version always flattens + def test_nested_with_state(self): + pass + class TestUpdateWrapper(unittest.TestCase): def check_wrapper(self, wrapper, wrapped, Index: Modules/_functoolsmodule.c =================================================================== --- Modules/_functoolsmodule.c (revision 82374) +++ Modules/_functoolsmodule.c (working copy) @@ -25,7 +25,7 @@ static PyObject * partial_new(PyTypeObject *type, PyObject *args, PyObject *kw) { - PyObject *func; + PyObject *func, *pargs, *nargs, *pkw; partialobject *pto; if (PyTuple_GET_SIZE(args) < 1) { @@ -34,7 +34,16 @@ return NULL; } + pargs = pkw = Py_None; func = PyTuple_GET_ITEM(args, 0); + if (Py_TYPE(func) == &partial_type && type == &partial_type) { + partialobject *part = (partialobject *)func; + if (part->dict == NULL) { + pargs = part->args; + pkw = part->kw; + func = part->fn; + } + } if (!PyCallable_Check(func)) { PyErr_SetString(PyExc_TypeError, "the first argument must be callable"); @@ -48,22 +57,54 @@ pto->fn = func; Py_INCREF(func); - pto->args = PyTuple_GetSlice(args, 1, PY_SSIZE_T_MAX); - if (pto->args == NULL) { + + nargs = PyTuple_GetSlice(args, 1, PY_SSIZE_T_MAX); + if (nargs == NULL) { + pto->args = NULL; pto->kw = NULL; Py_DECREF(pto); return NULL; } + if (pargs == Py_None || PyTuple_GET_SIZE(pargs) == 0) { + pto->args = nargs; + Py_INCREF(nargs); + } + else if (PyTuple_GET_SIZE(nargs) == 0) { + pto->args = pargs; + Py_INCREF(pargs); + } + else { + pto->args = PySequence_Concat(pargs, nargs); + if (pto->args == NULL) { + pto->kw = NULL; + Py_DECREF(pto); + return NULL; + } + } + Py_DECREF(nargs); + if (kw != NULL) { - pto->kw = PyDict_Copy(kw); + if (pkw == Py_None) { + pto->kw = PyDict_Copy(kw); + } + else { + pto->kw = PyDict_Copy(pkw); + if (pto->kw != NULL) { + if (PyDict_Merge(pto->kw, kw, 1) != 0) { + Py_DECREF(pto); + return NULL; + } + } + } if (pto->kw == NULL) { Py_DECREF(pto); return NULL; } - } else { - pto->kw = Py_None; - Py_INCREF(Py_None); } + else { + pto->kw = pkw; + Py_INCREF(pkw); + } pto->weakreflist = NULL; pto->dict = NULL;