Index: Python/ceval.c =================================================================== --- Python/ceval.c (revision 77958) +++ Python/ceval.c (working copy) @@ -145,6 +145,7 @@ static void format_exc_check_arg(PyObject *, char *, PyObject *); static PyObject * string_concatenate(PyObject *, PyObject *, PyFrameObject *, unsigned char *); +static PyObject * kwd_as_string(PyObject *); #define NAME_ERROR_MSG \ "name '%.200s' is not defined" @@ -2831,7 +2832,8 @@ PyObject *keyword = kws[2*i]; PyObject *value = kws[2*i + 1]; int j; - if (keyword == NULL || !PyString_Check(keyword)) { + if (keyword == NULL || !(PyString_Check(keyword) || + PyUnicode_Check(keyword))) { PyErr_Format(PyExc_TypeError, "%.200s() keywords must be strings", PyString_AsString(co->co_name)); @@ -2860,11 +2862,15 @@ goto fail; if (j >= co->co_argcount) { if (kwdict == NULL) { - PyErr_Format(PyExc_TypeError, - "%.200s() got an unexpected " - "keyword argument '%.400s'", - PyString_AsString(co->co_name), - PyString_AsString(keyword)); + PyObject *kwd_str = kwd_as_string(keyword); + if (kwd_str) { + PyErr_Format(PyExc_TypeError, + "%.200s() got an unexpected " + "keyword argument '%.400s'", + PyString_AsString(co->co_name), + PyString_AsString(kwd_str)); + Py_DECREF(kwd_str); + } goto fail; } PyDict_SetItem(kwdict, keyword, value); @@ -2872,12 +2878,16 @@ } kw_found: if (GETLOCAL(j) != NULL) { - PyErr_Format(PyExc_TypeError, - "%.200s() got multiple " - "values for keyword " - "argument '%.400s'", - PyString_AsString(co->co_name), - PyString_AsString(keyword)); + PyObject *kwd_str = kwd_as_string(keyword); + if (kwd_str) { + PyErr_Format(PyExc_TypeError, + "%.200s() got multiple " + "values for keyword " + "argument '%.400s'", + PyString_AsString(co->co_name), + PyString_AsString(kwd_str)); + Py_DECREF(kwd_str); + } goto fail; } Py_INCREF(value); @@ -3004,6 +3014,17 @@ } +static PyObject * +kwd_as_string(PyObject *kwd) { + if (PyString_Check(kwd)) { + Py_INCREF(kwd); + return kwd; + } + else + return _PyUnicode_AsDefaultEncodedString(kwd, "replace"); +} + + /* Implementation notes for set_exc_info() and reset_exc_info(): - Below, 'exc_ZZZ' stands for 'exc_type', 'exc_value' and Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 77958) +++ Misc/NEWS (working copy) @@ -12,6 +12,9 @@ Core and Builtins ----------------- +- Issue #4978: Passing keyword arguments as unicode strings is now allowed. + (Backported from r68805 on trunk.) + - Issue #7819: Check sys.call_tracing() arguments types. - Issue #7788: Fix an interpreter crash produced by deleting a list @@ -29,7 +32,8 @@ used to drop the time part of the result. - Issue #6108: unicode(exception) and str(exception) should return the same - message when only __str__ (and not __unicode__) is overridden in the subclass. + message when only __str__ (and not __unicode__) is overridden in the + subclass. - Issue #7491: Metaclass's __cmp__ method was ignored. Index: Lib/test/test_extcall.py =================================================================== --- Lib/test/test_extcall.py (revision 77958) +++ Lib/test/test_extcall.py (working copy) @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- """Doctest for method/function calls. We're going the use these types for extra testing @@ -252,11 +253,30 @@ """ +import unittest from test import test_support + +class UnicodeKeywordArgsTest(unittest.TestCase): + + def test_unicode_keywords(self): + def f(a): + return a + self.assertEqual(f(**{u'a': 4}), 4) + self.assertRaises(TypeError, f, **{u'stören': 4}) + self.assertRaises(TypeError, f, **{u'someLongString':2}) + try: + f(a=4, **{u'a': 4}) + except TypeError: + pass + else: + self.fail("duplicate arguments didn't raise") + + def test_main(): from test import test_extcall # self import test_support.run_doctest(test_extcall, True) + test_support.run_unittest(UnicodeKeywordArgsTest) if __name__ == '__main__': test_main()