Index: Objects/unicodeobject.c =================================================================== --- Objects/unicodeobject.c (revision 79036) +++ Objects/unicodeobject.c (working copy) @@ -8598,6 +8598,11 @@ PyUnicode_GET_SIZE(format_spec)); } +PyDoc_STRVAR(format_using_mapping__doc__, + "S.format_using_mapping(mapping, *args) -> str\n\ +\n\ +"); + PyDoc_STRVAR(p_format__doc__, "S.__format__(format_spec) -> str\n\ \n\ @@ -8666,6 +8671,7 @@ {"isprintable", (PyCFunction) unicode_isprintable, METH_NOARGS, isprintable__doc__}, {"zfill", (PyCFunction) unicode_zfill, METH_VARARGS, zfill__doc__}, {"format", (PyCFunction) do_string_format, METH_VARARGS | METH_KEYWORDS, format__doc__}, + {"format_using_mapping", (PyCFunction) do_string_format_using_mapping, METH_VARARGS, format_using_mapping__doc__}, {"__format__", (PyCFunction) unicode__format__, METH_VARARGS, p_format__doc__}, {"_formatter_field_name_split", (PyCFunction) formatter_field_name_split, METH_NOARGS}, {"_formatter_parser", (PyCFunction) formatter_parser, METH_NOARGS}, Index: Objects/stringlib/string_format.h =================================================================== --- Objects/stringlib/string_format.h (revision 79036) +++ Objects/stringlib/string_format.h (working copy) @@ -495,7 +495,8 @@ PyObject *key = SubString_new_object(&first); if (key == NULL) goto error; - if ((kwargs == NULL) || (obj = PyDict_GetItem(kwargs, key)) == NULL) { + if ((kwargs == NULL) || + (obj = PyObject_GetItem(kwargs, key)) == NULL) { PyErr_SetObject(PyExc_KeyError, key); Py_DECREF(key); goto error; @@ -1035,8 +1036,18 @@ return build_string(&input, args, kwargs, recursion_depth, &auto_number); } +static PyObject * +do_string_format_using_mapping(PyObject *self, PyObject *args) +{ + PyObject *mapping = NULL; + if (PyTuple_Size(args) == 0) + mapping = PyDict_New(); + else + mapping = PyTuple_GetItem(args, 0); + args = PyTuple_GetSlice(args, 1, PyTuple_Size(args)); + return do_string_format(self, args, mapping); +} - /************************************************************************/ /*********** formatteriterator ******************************************/ /************************************************************************/ Index: Lib/test/test_unicode.py =================================================================== --- Lib/test/test_unicode.py (revision 79036) +++ Lib/test/test_unicode.py (working copy) @@ -709,6 +709,260 @@ self.assertRaises(ValueError, format, '', '#') self.assertRaises(ValueError, format, '', '#20') + def test_format_using_mapping(self): + self.assertEqual(''.format_using_mapping({}), '') + self.assertEqual('a'.format_using_mapping({}), 'a') + self.assertEqual('ab'.format_using_mapping({}), 'ab') + self.assertEqual('a{{'.format_using_mapping({}), 'a{') + self.assertEqual('a}}'.format_using_mapping({}), 'a}') + self.assertEqual('{{b'.format_using_mapping({}), '{b') + self.assertEqual('}}b'.format_using_mapping({}), '}b') + self.assertEqual('a{{b'.format_using_mapping({}), 'a{b') + + # examples from the PEP: + import datetime + self.assertEqual("My name is {0}".format_using_mapping({}, 'Fred'), "My name is Fred") + self.assertEqual("My name is {0[name]}".format_using_mapping({}, dict(name='Fred')), + "My name is Fred") + self.assertEqual("My name is {0} :-{{}}".format_using_mapping({}, 'Fred'), + "My name is Fred :-{}") + + d = datetime.date(2007, 8, 18) + self.assertEqual("The year is {0.year}".format_using_mapping({}, d), + "The year is 2007") + + # using mappings + class Mapping(dict): + def __missing__(self, key): + return key + self.assertEqual('{hello}'.format_using_mapping(Mapping()), 'hello') + self.assertEqual('{a} {world}'.format_using_mapping(Mapping(a='hello')), 'hello world') + + class InternalMapping: + def __init__(self): + self.mapping = {'a': 'hello'} + def __getitem__(self, key): + return self.mapping[key] + self.assertEqual('{a} {0}'.format_using_mapping(InternalMapping(), 'world'), 'hello world') + + + # classes we'll use for testing + class C: + def __init__(self, x=100): + self._x = x + def __format__(self, spec): + return spec + + class D: + def __init__(self, x): + self.x = x + def __format__(self, spec): + return str(self.x) + + # class with __str__, but no __format__ + class E: + def __init__(self, x): + self.x = x + def __str__(self): + return 'E(' + self.x + ')' + + # class with __repr__, but no __format__ or __str__ + class F: + def __init__(self, x): + self.x = x + def __repr__(self): + return 'F(' + self.x + ')' + + # class with __format__ that forwards to string, for some format_spec's + class G: + def __init__(self, x): + self.x = x + def __str__(self): + return "string is " + self.x + def __format__(self, format_spec): + if format_spec == 'd': + return 'G(' + self.x + ')' + return object.__format__(self, format_spec) + + # class that returns a bad type from __format__ + class H: + def __format__(self, format_spec): + return 1.0 + + class I(datetime.date): + def __format__(self, format_spec): + return self.strftime(format_spec) + + class J(int): + def __format__(self, format_spec): + return int.__format__(self * 2, format_spec) + + + self.assertEqual(''.format_using_mapping({}, ), '') + self.assertEqual('abc'.format_using_mapping({}, ), 'abc') + self.assertEqual('{0}'.format_using_mapping({}, 'abc'), 'abc') + self.assertEqual('{0:}'.format_using_mapping({}, 'abc'), 'abc') +# self.assertEqual('{ 0 }'.format_using_mapping({}, 'abc'), 'abc') + self.assertEqual('X{0}'.format_using_mapping({}, 'abc'), 'Xabc') + self.assertEqual('{0}X'.format_using_mapping({}, 'abc'), 'abcX') + self.assertEqual('X{0}Y'.format_using_mapping({}, 'abc'), 'XabcY') + self.assertEqual('{1}'.format_using_mapping({}, 1, 'abc'), 'abc') + self.assertEqual('X{1}'.format_using_mapping({}, 1, 'abc'), 'Xabc') + self.assertEqual('{1}X'.format_using_mapping({}, 1, 'abc'), 'abcX') + self.assertEqual('X{1}Y'.format_using_mapping({}, 1, 'abc'), 'XabcY') + self.assertEqual('{0}'.format_using_mapping({}, -15), '-15') + self.assertEqual('{0}{1}'.format_using_mapping({}, -15, 'abc'), '-15abc') + self.assertEqual('{0}X{1}'.format_using_mapping({}, -15, 'abc'), '-15Xabc') + self.assertEqual('{{'.format_using_mapping({}, ), '{') + self.assertEqual('}}'.format_using_mapping({}, ), '}') + self.assertEqual('{{}}'.format_using_mapping({}, ), '{}') + self.assertEqual('{{x}}'.format_using_mapping({}, ), '{x}') + self.assertEqual('{{{0}}}'.format_using_mapping({}, 123), '{123}') + self.assertEqual('{{{{0}}}}'.format_using_mapping({}, ), '{{0}}') + self.assertEqual('}}{{'.format_using_mapping({}, ), '}{') + self.assertEqual('}}x{{'.format_using_mapping({}, ), '}x{') + + # weird field names + self.assertEqual("{0[foo-bar]}".format_using_mapping({}, {'foo-bar':'baz'}), 'baz') + self.assertEqual("{0[foo bar]}".format_using_mapping({}, {'foo bar':'baz'}), 'baz') + self.assertEqual("{0[ ]}".format_using_mapping({}, {' ':3}), '3') + + self.assertEqual('{foo._x}'.format_using_mapping({'foo': C(20)}), '20') + self.assertEqual('{1}{0}'.format_using_mapping({}, D(10), D(20)), '2010') + self.assertEqual('{0._x.x}'.format_using_mapping({}, C(D('abc'))), 'abc') + self.assertEqual('{0[0]}'.format_using_mapping({}, ['abc', 'def']), 'abc') + self.assertEqual('{0[1]}'.format_using_mapping({}, ['abc', 'def']), 'def') + self.assertEqual('{0[1][0]}'.format_using_mapping({}, ['abc', ['def']]), 'def') + self.assertEqual('{0[1][0].x}'.format_using_mapping({}, ['abc', [D('def')]]), 'def') + + # strings + self.assertEqual('{0:.3s}'.format_using_mapping({}, 'abc'), 'abc') + self.assertEqual('{0:.3s}'.format_using_mapping({}, 'ab'), 'ab') + self.assertEqual('{0:.3s}'.format_using_mapping({}, 'abcdef'), 'abc') + self.assertEqual('{0:.0s}'.format_using_mapping({}, 'abcdef'), '') + self.assertEqual('{0:3.3s}'.format_using_mapping({}, 'abc'), 'abc') + self.assertEqual('{0:2.3s}'.format_using_mapping({}, 'abc'), 'abc') + self.assertEqual('{0:2.2s}'.format_using_mapping({}, 'abc'), 'ab') + self.assertEqual('{0:3.2s}'.format_using_mapping({}, 'abc'), 'ab ') + self.assertEqual('{0:x<0s}'.format_using_mapping({}, 'result'), 'result') + self.assertEqual('{0:x<5s}'.format_using_mapping({}, 'result'), 'result') + self.assertEqual('{0:x<6s}'.format_using_mapping({}, 'result'), 'result') + self.assertEqual('{0:x<7s}'.format_using_mapping({}, 'result'), 'resultx') + self.assertEqual('{0:x<8s}'.format_using_mapping({}, 'result'), 'resultxx') + self.assertEqual('{0: <7s}'.format_using_mapping({}, 'result'), 'result ') + self.assertEqual('{0:<7s}'.format_using_mapping({}, 'result'), 'result ') + self.assertEqual('{0:>7s}'.format_using_mapping({}, 'result'), ' result') + self.assertEqual('{0:>8s}'.format_using_mapping({}, 'result'), ' result') + self.assertEqual('{0:^8s}'.format_using_mapping({}, 'result'), ' result ') + self.assertEqual('{0:^9s}'.format_using_mapping({}, 'result'), ' result ') + self.assertEqual('{0:^10s}'.format_using_mapping({}, 'result'), ' result ') + self.assertEqual('{0:10000}'.format_using_mapping({}, 'a'), 'a' + ' ' * 9999) + self.assertEqual('{0:10000}'.format_using_mapping({}, ''), ' ' * 10000) + self.assertEqual('{0:10000000}'.format_using_mapping({}, ''), ' ' * 10000000) + + # format specifiers for user defined type + self.assertEqual('{0:abc}'.format_using_mapping({}, C()), 'abc') + + # !r, !s and !a coercions + self.assertEqual('{0!s}'.format_using_mapping({}, 'Hello'), 'Hello') + self.assertEqual('{0!s:}'.format_using_mapping({}, 'Hello'), 'Hello') + self.assertEqual('{0!s:15}'.format_using_mapping({}, 'Hello'), 'Hello ') + self.assertEqual('{0!s:15s}'.format_using_mapping({}, 'Hello'), 'Hello ') + self.assertEqual('{0!r}'.format_using_mapping({}, 'Hello'), "'Hello'") + self.assertEqual('{0!r:}'.format_using_mapping({}, 'Hello'), "'Hello'") + self.assertEqual('{0!r}'.format_using_mapping({}, F('Hello')), 'F(Hello)') + self.assertEqual('{0!r}'.format_using_mapping({}, '\u0378'), "'\\u0378'") # nonprintable + self.assertEqual('{0!r}'.format_using_mapping({}, '\u0374'), "'\u0374'") # printable + self.assertEqual('{0!r}'.format_using_mapping({}, F('\u0374')), 'F(\u0374)') + self.assertEqual('{0!a}'.format_using_mapping({}, 'Hello'), "'Hello'") + self.assertEqual('{0!a}'.format_using_mapping({}, '\u0378'), "'\\u0378'") # nonprintable + self.assertEqual('{0!a}'.format_using_mapping({}, '\u0374'), "'\\u0374'") # printable + self.assertEqual('{0!a:}'.format_using_mapping({}, 'Hello'), "'Hello'") + self.assertEqual('{0!a}'.format_using_mapping({}, F('Hello')), 'F(Hello)') + self.assertEqual('{0!a}'.format_using_mapping({}, F('\u0374')), 'F(\\u0374)') + + # test fallback to object.__format__ + self.assertEqual('{0}'.format_using_mapping({}, {}), '{}') + self.assertEqual('{0}'.format_using_mapping({}, []), '[]') + self.assertEqual('{0}'.format_using_mapping({}, [1]), '[1]') + self.assertEqual('{0}'.format_using_mapping({}, E('data')), 'E(data)') + self.assertEqual('{0:^10}'.format_using_mapping({}, E('data')), ' E(data) ') + self.assertEqual('{0:^10s}'.format_using_mapping({}, E('data')), ' E(data) ') + self.assertEqual('{0:d}'.format_using_mapping({}, G('data')), 'G(data)') + self.assertEqual('{0:>15s}'.format_using_mapping({}, G('data')), ' string is data') + self.assertEqual('{0!s}'.format_using_mapping({}, G('data')), 'string is data') + + self.assertEqual("{0:date: %Y-%m-%d}".format_using_mapping({}, I(year=2007, + month=8, + day=27)), + "date: 2007-08-27") + + # test deriving from a builtin type and overriding __format__ + self.assertEqual("{0}".format_using_mapping({}, J(10)), "20") + + + # string format specifiers + self.assertEqual('{0:}'.format_using_mapping({}, 'a'), 'a') + + # computed format specifiers + self.assertEqual("{0:.{1}}".format_using_mapping({}, 'hello world', 5), 'hello') + self.assertEqual("{0:.{1}s}".format_using_mapping({}, 'hello world', 5), 'hello') + self.assertEqual("{0:.{precision}s}".format_using_mapping({'precision': 5}, 'hello world'), 'hello') + self.assertEqual("{0:{width}.{precision}s}".format_using_mapping({'width': 10, 'precision': 5}, 'hello world'), 'hello ') + self.assertEqual("{0:{width}.{precision}s}".format_using_mapping({'width': 10, 'precision': 5}, 'hello world'), 'hello ') + + # test various errors + self.assertRaises(ValueError, '{'.format_using_mapping) + self.assertRaises(ValueError, '}'.format_using_mapping) + self.assertRaises(ValueError, 'a{'.format_using_mapping) + self.assertRaises(ValueError, 'a}'.format_using_mapping) + self.assertRaises(ValueError, '{a'.format_using_mapping) + self.assertRaises(ValueError, '}a'.format_using_mapping) + self.assertRaises(IndexError, '{0}'.format_using_mapping) + self.assertRaises(IndexError, '{1}'.format_using_mapping, {}, 'abc') + self.assertRaises(KeyError, '{x}'.format_using_mapping) + self.assertRaises(ValueError, "}{".format_using_mapping) + self.assertRaises(ValueError, "{".format_using_mapping) + self.assertRaises(ValueError, "}".format_using_mapping) + self.assertRaises(ValueError, "abc{0:{}".format_using_mapping) + self.assertRaises(ValueError, "{0".format_using_mapping) + self.assertRaises(IndexError, "{0.}".format_using_mapping) + self.assertRaises(ValueError, "{0.}".format_using_mapping, {}, 0) + self.assertRaises(IndexError, "{0[}".format_using_mapping) + self.assertRaises(ValueError, "{0[}".format_using_mapping, {}, []) + self.assertRaises(KeyError, "{0]}".format_using_mapping) + self.assertRaises(ValueError, "{0.[]}".format_using_mapping, {}, 0) + self.assertRaises(ValueError, "{0..foo}".format_using_mapping, {}, 0) + self.assertRaises(ValueError, "{0[0}".format_using_mapping, {}, 0) + self.assertRaises(ValueError, "{0[0:foo}".format_using_mapping, {}, 0) + self.assertRaises(KeyError, "{c]}".format_using_mapping) + self.assertRaises(ValueError, "{{ {{{0}}".format_using_mapping, {}, 0) + self.assertRaises(ValueError, "{0}}".format_using_mapping, {}, 0) + self.assertRaises(KeyError, "{foo}".format_using_mapping, {'bar': 3}) + self.assertRaises(ValueError, "{0!x}".format_using_mapping, {}, 3) + self.assertRaises(ValueError, "{0!}".format_using_mapping, {}, 0) + self.assertRaises(ValueError, "{0!rs}".format_using_mapping, {}, 0) + self.assertRaises(ValueError, "{!}".format_using_mapping) + self.assertRaises(IndexError, "{:}".format_using_mapping) + self.assertRaises(IndexError, "{:s}".format_using_mapping) + self.assertRaises(IndexError, "{}".format_using_mapping) + + # issue 6089 + self.assertRaises(ValueError, "{0[0]x}".format_using_mapping, {}, [None]) + self.assertRaises(ValueError, "{0[0](10)}".format_using_mapping, {}, [None]) + + # can't have a replacement on the field name portion + self.assertRaises(TypeError, '{0[{1}]}'.format_using_mapping, {}, 'abcdefg', 4) + + # exceed maximum recursion depth + self.assertRaises(ValueError, "{0:{1:{2}}}".format_using_mapping, {}, 'abc', 's', '') + self.assertRaises(ValueError, "{0:{1:{2:{3:{4:{5:{6}}}}}}}".format_using_mapping, + {}, 0, 1, 2, 3, 4, 5, 6, 7) + + # string format_using_mapping spec errors + self.assertRaises(ValueError, "{0:-s}".format_using_mapping, {}, '') + self.assertRaises(ValueError, "{0:=s}".format_using_mapping, {}, '') + def test_format_auto_numbering(self): class C: def __init__(self, x=100): @@ -739,6 +993,36 @@ self.assertEqual('{:{f}}{g}{}'.format(1, 3, g='g', f=2), ' 1g3') self.assertEqual('{f:{}}{}{g}'.format(2, 4, f=1, g='g'), ' 14g') + def test_format_using_mapping_auto_numbering(self): + class C: + def __init__(self, x=100): + self._x = x + def __format__(self, spec): + return spec + + self.assertEqual('{}'.format_using_mapping({}, 10), '10') + self.assertEqual('{:5}'.format_using_mapping({}, 's'), 's ') + self.assertEqual('{!r}'.format_using_mapping({}, 's'), "'s'") + self.assertEqual('{._x}'.format_using_mapping({}, C(10)), '10') + self.assertEqual('{[1]}'.format_using_mapping({}, [1, 2]), '2') + self.assertEqual('{[a]}'.format_using_mapping({}, {'a':4, 'b':2}), '4') + self.assertEqual('a{}b{}c'.format_using_mapping({}, 0, 1), 'a0b1c') + + self.assertEqual('a{:{}}b'.format_using_mapping({}, 'x', '^10'), 'a x b') + self.assertEqual('a{:{}x}b'.format_using_mapping({}, 20, '#'), 'a0x14b') + + # can't mix and match numbering and auto-numbering + self.assertRaises(ValueError, '{}{1}'.format_using_mapping, {}, 1, 2) + self.assertRaises(ValueError, '{1}{}'.format_using_mapping, {}, 1, 2) + self.assertRaises(ValueError, '{:{1}}'.format_using_mapping, {}, 1, 2) + self.assertRaises(ValueError, '{0:{}}'.format_using_mapping, {}, 1, 2) + + # can mix and match auto-numbering and named + self.assertEqual('{f}{}'.format_using_mapping({'f': 'test'}, 4), 'test4') + self.assertEqual('{}{f}'.format_using_mapping({'f': 'test'}, 4), '4test') + self.assertEqual('{:{f}}{g}{}'.format_using_mapping({'g': 'g', 'f': 2}, 1, 3), ' 1g3') + self.assertEqual('{f:{}}{}{g}'.format_using_mapping({'f': 1, 'g': 'g'}, 2, 4), ' 14g') + def test_formatting(self): string_tests.MixinStrUnicodeUserStringTest.test_formatting(self) # Testing Unicode formatting strings...