diff options
-rw-r--r-- | Doc/library/stdtypes.rst | 8 | ||||
-rw-r--r-- | Lib/test/test_unicode.py | 78 | ||||
-rw-r--r-- | Misc/NEWS | 2 | ||||
-rw-r--r-- | Objects/stringlib/string_format.h | 11 | ||||
-rw-r--r-- | Objects/unicodeobject.c | 6 |
5 files changed, 104 insertions, 1 deletions
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index e75cfc7..0a3ba99 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -1038,6 +1038,14 @@ functions based on regular expressions. that can be specified in format strings. +.. method:: str.format_map(mapping) + + Similar to ``str.forrmat(**mapping)``, except that ``mapping`` is + used directly and not copied to a :class:`dict` . This is useful + if for example ``mapping`` is a dict subclass. + + .. versionadded:: 3.2 + .. method:: str.index(sub[, start[, end]]) Like :meth:`find`, but raise :exc:`ValueError` when the substring is not found. diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index 7927834..cc891bd 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -695,6 +695,84 @@ class UnicodeTest(string_tests.CommonTest, self.assertRaises(ValueError, format, '', '#') self.assertRaises(ValueError, format, '', '#20') + def test_format_map(self): + self.assertEqual(''.format_map({}), '') + self.assertEqual('a'.format_map({}), 'a') + self.assertEqual('ab'.format_map({}), 'ab') + self.assertEqual('a{{'.format_map({}), 'a{') + self.assertEqual('a}}'.format_map({}), 'a}') + self.assertEqual('{{b'.format_map({}), '{b') + self.assertEqual('}}b'.format_map({}), '}b') + self.assertEqual('a{{b'.format_map({}), 'a{b') + + # using mappings + class Mapping(dict): + def __missing__(self, key): + return key + self.assertEqual('{hello}'.format_map(Mapping()), 'hello') + self.assertEqual('{a} {world}'.format_map(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}'.format_map(InternalMapping()), 'hello') + + + # 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 + + self.assertEqual('{foo._x}'.format_map({'foo': C(20)}), '20') + + # test various errors + self.assertRaises(TypeError, '{'.format_map) + self.assertRaises(TypeError, '}'.format_map) + self.assertRaises(TypeError, 'a{'.format_map) + self.assertRaises(TypeError, 'a}'.format_map) + self.assertRaises(TypeError, '{a'.format_map) + self.assertRaises(TypeError, '}a'.format_map) + def test_format_auto_numbering(self): class C: def __init__(self, x=100): @@ -10,6 +10,8 @@ What's New in Python 3.2 Beta 1? Core and Builtins ----------------- +- Issue #6081: Add str.format_map, similar to str.format(**mapping). + - If FileIO.__init__ fails, close the file descriptor. - Issue #10221: dict.pop(k) now has a key error message that includes the diff --git a/Objects/stringlib/string_format.h b/Objects/stringlib/string_format.h index 126c870..5205aa9 100644 --- a/Objects/stringlib/string_format.h +++ b/Objects/stringlib/string_format.h @@ -499,7 +499,11 @@ get_field_object(SubString *input, PyObject *args, PyObject *kwargs, PyObject *key = SubString_new_object(&first); if (key == NULL) goto error; - if ((kwargs == NULL) || (obj = PyDict_GetItem(kwargs, key)) == NULL) { + + /* Use PyObject_GetItem instead of PyDict_GetItem because this + code is no longer just used with kwargs. It might be passed + a non-dict when called through format_map. */ + if ((kwargs == NULL) || (obj = PyObject_GetItem(kwargs, key)) == NULL) { PyErr_SetObject(PyExc_KeyError, key); Py_DECREF(key); goto error; @@ -1039,6 +1043,11 @@ do_string_format(PyObject *self, PyObject *args, PyObject *kwargs) return build_string(&input, args, kwargs, recursion_depth, &auto_number); } +static PyObject * +do_string_format_map(PyObject *self, PyObject *obj) +{ + return do_string_format(self, NULL, obj); +} /************************************************************************/ diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 17dc27e..b67b8f9 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -9028,6 +9028,11 @@ PyDoc_STRVAR(format__doc__, \n\ "); +PyDoc_STRVAR(format_map__doc__, + "S.format_map(mapping) -> str\n\ +\n\ +"); + static PyObject * unicode__format__(PyObject* self, PyObject* args) { @@ -9109,6 +9114,7 @@ static PyMethodDef unicode_methods[] = { {"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_map", (PyCFunction) do_string_format_map, METH_O, format_map__doc__}, {"__format__", (PyCFunction) unicode__format__, METH_VARARGS, p_format__doc__}, {"maketrans", (PyCFunction) unicode_maketrans, METH_VARARGS | METH_STATIC, maketrans__doc__}, |