diff options
author | Eric Smith <eric@trueblade.com> | 2010-09-13 20:48:43 (GMT) |
---|---|---|
committer | Eric Smith <eric@trueblade.com> | 2010-09-13 20:48:43 (GMT) |
commit | e4d6317c8725f9f341c6f2dd628e3b3ac79ef309 (patch) | |
tree | 708a741bc323f4ed96680a02659355adfd2159f0 | |
parent | af9d10aa30fc1e3dd043519b2ea145c4a06c9199 (diff) | |
download | cpython-e4d6317c8725f9f341c6f2dd628e3b3ac79ef309.zip cpython-e4d6317c8725f9f341c6f2dd628e3b3ac79ef309.tar.gz cpython-e4d6317c8725f9f341c6f2dd628e3b3ac79ef309.tar.bz2 |
Issue 7994: Make object.__format__() raise a PendingDeprecationWarning
if the format string is not empty. Manually merge r79596 and r84772
from 2.x.
Also, apparently test_format() from test_builtin never made it into
3.x. I've added it as well. It tests the basic format()
infrastructure.
-rw-r--r-- | Lib/test/test_builtin.py | 110 | ||||
-rw-r--r-- | Lib/test/test_unicode.py | 11 | ||||
-rw-r--r-- | Misc/NEWS | 8 | ||||
-rw-r--r-- | Objects/typeobject.c | 23 |
4 files changed, 145 insertions, 7 deletions
diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 35b652b..f00091b 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -1279,6 +1279,116 @@ class BuiltinTest(unittest.TestCase): return i self.assertRaises(ValueError, list, zip(BadSeq(), BadSeq())) + def test_format(self): + # Test the basic machinery of the format() builtin. Don't test + # the specifics of the various formatters + self.assertEqual(format(3, ''), '3') + + # Returns some classes to use for various tests. There's + # an old-style version, and a new-style version + def classes_new(): + class A(object): + def __init__(self, x): + self.x = x + def __format__(self, format_spec): + return str(self.x) + format_spec + class DerivedFromA(A): + pass + + class Simple(object): pass + class DerivedFromSimple(Simple): + def __init__(self, x): + self.x = x + def __format__(self, format_spec): + return str(self.x) + format_spec + class DerivedFromSimple2(DerivedFromSimple): pass + return A, DerivedFromA, DerivedFromSimple, DerivedFromSimple2 + + def class_test(A, DerivedFromA, DerivedFromSimple, DerivedFromSimple2): + self.assertEqual(format(A(3), 'spec'), '3spec') + self.assertEqual(format(DerivedFromA(4), 'spec'), '4spec') + self.assertEqual(format(DerivedFromSimple(5), 'abc'), '5abc') + self.assertEqual(format(DerivedFromSimple2(10), 'abcdef'), + '10abcdef') + + class_test(*classes_new()) + + def empty_format_spec(value): + # test that: + # format(x, '') == str(x) + # format(x) == str(x) + self.assertEqual(format(value, ""), str(value)) + self.assertEqual(format(value), str(value)) + + # for builtin types, format(x, "") == str(x) + empty_format_spec(17**13) + empty_format_spec(1.0) + empty_format_spec(3.1415e104) + empty_format_spec(-3.1415e104) + empty_format_spec(3.1415e-104) + empty_format_spec(-3.1415e-104) + empty_format_spec(object) + empty_format_spec(None) + + # TypeError because self.__format__ returns the wrong type + class BadFormatResult: + def __format__(self, format_spec): + return 1.0 + self.assertRaises(TypeError, format, BadFormatResult(), "") + + # TypeError because format_spec is not unicode or str + self.assertRaises(TypeError, format, object(), 4) + self.assertRaises(TypeError, format, object(), object()) + + # tests for object.__format__ really belong elsewhere, but + # there's no good place to put them + x = object().__format__('') + self.assertTrue(x.startswith('<object object at')) + + # first argument to object.__format__ must be string + self.assertRaises(TypeError, object().__format__, 3) + self.assertRaises(TypeError, object().__format__, object()) + self.assertRaises(TypeError, object().__format__, None) + + # -------------------------------------------------------------------- + # Issue #7994: object.__format__ with a non-empty format string is + # pending deprecated + def test_deprecated_format_string(obj, fmt_str, should_raise_warning): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always", PendingDeprecationWarning) + format(obj, fmt_str) + if should_raise_warning: + self.assertEqual(len(w), 1) + self.assertIsInstance(w[0].message, PendingDeprecationWarning) + self.assertIn('object.__format__ with a non-empty format ' + 'string', str(w[0].message)) + else: + self.assertEqual(len(w), 0) + + fmt_strs = ['', 's'] + + class A: + def __format__(self, fmt_str): + return format('', fmt_str) + + for fmt_str in fmt_strs: + test_deprecated_format_string(A(), fmt_str, False) + + class B: + pass + + class C(object): + pass + + for cls in [object, B, C]: + for fmt_str in fmt_strs: + test_deprecated_format_string(cls(), fmt_str, len(fmt_str) != 0) + # -------------------------------------------------------------------- + + # make sure we can take a subclass of str as a format spec + class DerivedFromStr(str): pass + self.assertEqual(format(0, DerivedFromStr('10')), ' 0') + def test_bin(self): self.assertEqual(bin(0), '0b0') self.assertEqual(bin(1), '0b1') diff --git a/Lib/test/test_unicode.py b/Lib/test/test_unicode.py index 0ada5cc..f6c38dd 100644 --- a/Lib/test/test_unicode.py +++ b/Lib/test/test_unicode.py @@ -609,13 +609,16 @@ class UnicodeTest(string_tests.CommonTest, self.assertEqual('{0}'.format({}), '{}') self.assertEqual('{0}'.format([]), '[]') self.assertEqual('{0}'.format([1]), '[1]') - self.assertEqual('{0}'.format(E('data')), 'E(data)') - self.assertEqual('{0:^10}'.format(E('data')), ' E(data) ') - self.assertEqual('{0:^10s}'.format(E('data')), ' E(data) ') + self.assertEqual('{0:d}'.format(G('data')), 'G(data)') - self.assertEqual('{0:>15s}'.format(G('data')), ' string is data') self.assertEqual('{0!s}'.format(G('data')), 'string is data') + msg = 'object.__format__ with a non-empty format string is deprecated' + with support.check_warnings((msg, PendingDeprecationWarning)): + self.assertEqual('{0:^10}'.format(E('data')), ' E(data) ') + self.assertEqual('{0:^10s}'.format(E('data')), ' E(data) ') + self.assertEqual('{0:>15s}'.format(G('data')), ' string is data') + self.assertEqual("{0:date: %Y-%m-%d}".format(I(year=2007, month=8, day=27)), @@ -10,6 +10,14 @@ What's New in Python 3.2 Alpha 3? Core and Builtins ----------------- +- Issue #7994: Issue a PendingDeprecationWarning if object.__format__ + is called with a non-empty format string. This is an effort to + future-proof user code. If a derived class does not currently + implement __format__ but later adds its own __format__, it would + most likely break user code that had supplied a format string. This + will be changed to a DeprecationWaring in Python 3.3 and it will be + an error in Python 3.4. + - Issue #9828: Destroy the GIL in Py_Finalize(), so that it gets properly re-created on a subsequent call to Py_Initialize(). The problem (a crash) wouldn't appear in 3.1 or 2.7 where the GIL's structure is more trivial. diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 8b74e1e..897374d 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3315,9 +3315,26 @@ object_format(PyObject *self, PyObject *args) return NULL; self_as_str = PyObject_Str(self); - if (self_as_str != NULL) - result = PyObject_Format(self_as_str, format_spec); - + if (self_as_str != NULL) { + /* Issue 7994: If we're converting to a string, we + should reject format specifications */ + if (PyUnicode_GET_SIZE(format_spec) > 0) { + if (PyErr_WarnEx(PyExc_PendingDeprecationWarning, + "object.__format__ with a non-empty format " + "string is deprecated", 1) < 0) { + goto done; + } + /* Eventually this will become an error: + PyErr_Format(PyExc_TypeError, + "non-empty format string passed to object.__format__"); + goto done; + */ + } + + result = PyObject_Format(self_as_str, format_spec); + } + +done: Py_XDECREF(self_as_str); return result; |