summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEric Smith <eric@trueblade.com>2010-09-13 20:48:43 (GMT)
committerEric Smith <eric@trueblade.com>2010-09-13 20:48:43 (GMT)
commite4d6317c8725f9f341c6f2dd628e3b3ac79ef309 (patch)
tree708a741bc323f4ed96680a02659355adfd2159f0
parentaf9d10aa30fc1e3dd043519b2ea145c4a06c9199 (diff)
downloadcpython-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.py110
-rw-r--r--Lib/test/test_unicode.py11
-rw-r--r--Misc/NEWS8
-rw-r--r--Objects/typeobject.c23
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)),
diff --git a/Misc/NEWS b/Misc/NEWS
index 04f5799..0b86bc9 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -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;