Index: Python/ceval.c =================================================================== --- Python/ceval.c (revision 54869) +++ Python/ceval.c (working copy) @@ -3791,13 +3791,30 @@ if (flags & CALL_FLAG_KW) { kwdict = EXT_POP(*pp_stack); - if (!(kwdict && PyDict_Check(kwdict))) { - PyErr_Format(PyExc_TypeError, - "%s%s argument after ** " - "must be a dictionary", - PyEval_GetFuncName(func), - PyEval_GetFuncDesc(func)); - goto ext_call_fail; + if (!PyDict_Check(kwdict)) { + PyObject *d; + d = PyDict_New(); + if (d == NULL) + goto ext_call_fail; + if (PyDict_Update(d, kwdict) != 0) { + Py_DECREF(d); + /* PyDict_Update raises attribute + * error (percolated from an attempt + * to get 'keys' attribute) instead of + * a type error if its second argument + * is not a mapping. + */ + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Format(PyExc_TypeError, + "%s%s argument after ** " + "must be a mapping", + PyEval_GetFuncName(func), + PyEval_GetFuncDesc(func)); + } + goto ext_call_fail; + } + Py_DECREF(kwdict); + kwdict = d; } } if (flags & CALL_FLAG_VAR) { Index: Lib/test/test_extcall.py =================================================================== --- Lib/test/test_extcall.py (revision 54869) +++ Lib/test/test_extcall.py (working copy) @@ -1,5 +1,6 @@ from test.test_support import verify, verbose, TestFailed, sortdict from UserList import UserList +from UserDict import UserDict def e(a, b): print a, b @@ -25,6 +26,12 @@ f(1, 2, 3, *(4, 5), **{'a':6, 'b':7}) f(1, 2, 3, x=4, y=5, *(6, 7), **{'a':8, 'b':9}) + +f(1, 2, 3, **UserDict(a=4, b=5)) +f(1, 2, 3, *(4, 5), **UserDict(a=6, b=7)) +f(1, 2, 3, x=4, y=5, *(6, 7), **UserDict(a=8, b=9)) + + # Verify clearing of SF bug #733667 try: e(c=3) Index: Lib/test/output/test_extcall =================================================================== --- Lib/test/output/test_extcall (revision 54869) +++ Lib/test/output/test_extcall (working copy) @@ -9,6 +9,9 @@ (1, 2, 3) {'a': 4, 'b': 5} (1, 2, 3, 4, 5) {'a': 6, 'b': 7} (1, 2, 3, 6, 7) {'a': 8, 'b': 9, 'x': 4, 'y': 5} +(1, 2, 3) {'a': 4, 'b': 5} +(1, 2, 3, 4, 5) {'a': 6, 'b': 7} +(1, 2, 3, 6, 7) {'a': 8, 'b': 9, 'x': 4, 'y': 5} TypeError: g() takes at least 1 argument (0 given) TypeError: g() takes at least 1 argument (0 given) TypeError: g() takes at least 1 argument (0 given) @@ -28,9 +31,9 @@ h() argument after * must be a sequence dir() argument after * must be a sequence NoneType object argument after * must be a sequence -h() argument after ** must be a dictionary -dir() argument after ** must be a dictionary -NoneType object argument after ** must be a dictionary +h() argument after ** must be a mapping +dir() argument after ** must be a mapping +NoneType object argument after ** must be a mapping dir() got multiple values for keyword argument 'b' 3 512 True 3 Index: Doc/ref/ref5.tex =================================================================== --- Doc/ref/ref5.tex (revision 54869) +++ Doc/ref/ref5.tex (working copy) @@ -704,7 +704,7 @@ this confusion does not arise. If the syntax \samp{**expression} appears in the function call, -\samp{expression} must evaluate to a (subclass of) dictionary, the +\samp{expression} must evaluate to a mapping, the contents of which are treated as additional keyword arguments. In the case of a keyword appearing in both \samp{expression} and as an explicit keyword argument, a \exception{TypeError} exception is