From d44b2fc87c2d9bd9e390eb54d50306959d80ba87 Mon Sep 17 00:00:00 2001 From: Eric Smith Date: Fri, 2 Apr 2010 12:30:56 +0000 Subject: [PATCH] Issue 7994: Make object.__format__ with a non-empty format string a PendingDecprecationWarning. Still need to remove uses of this from various tests. --- Lib/test/test_builtin.py | 36 ++++++++++++++++++++++++++++++ Misc/NEWS | 8 +++++++ Objects/abstract.c | 47 ++++++++++++++++++++++++++++++---------- Objects/typeobject.c | 29 ++++++++++++++++++++++--- 4 files changed, 106 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_builtin.py b/Lib/test/test_builtin.py index 05686b91198..013731ceacd 100644 --- a/Lib/test/test_builtin.py +++ b/Lib/test/test_builtin.py @@ -4,6 +4,7 @@ import platform import unittest from test.test_support import fcmp, have_unicode, TESTFN, unlink, \ run_unittest, check_py3k_warnings +import warnings from operator import neg import sys, cStringIO, random, UserDict @@ -1483,6 +1484,41 @@ class BuiltinTest(unittest.TestCase): 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', u'', u'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') diff --git a/Misc/NEWS b/Misc/NEWS index b91bd74ed38..cd6f4abcece 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -12,6 +12,14 @@ What's New in Python 2.7 beta 1? 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 #8268: Old-style classes (not just instances) now support weak references. diff --git a/Objects/abstract.c b/Objects/abstract.c index 8d6f023c47b..0b541ee7488 100644 --- a/Objects/abstract.c +++ b/Objects/abstract.c @@ -777,8 +777,9 @@ PyObject_Format(PyObject* obj, PyObject *format_spec) NULL); Py_DECREF(bound_method); } else { - PyObject *self_as_str; - PyObject *format_method; + PyObject *self_as_str = NULL; + PyObject *format_method = NULL; + Py_ssize_t format_len; PyErr_Clear(); /* Per the PEP, convert to str (or unicode, @@ -786,29 +787,53 @@ PyObject_Format(PyObject* obj, PyObject *format_spec) specifier). For new-style classes, this logic is done by object.__format__(). */ #ifdef Py_USING_UNICODE - if (spec_is_unicode) + if (spec_is_unicode) { + format_len = PyUnicode_GET_SIZE(format_spec); self_as_str = PyObject_Unicode(obj); - else + } else #endif + { + format_len = PyString_GET_SIZE(format_spec); self_as_str = PyObject_Str(obj); + } if (self_as_str == NULL) - goto done; + goto done1; + + if (format_len > 0) { + /* See the almost identical code in + typeobject.c for new-style + classes. */ + if (PyErr_WarnEx( + PyExc_PendingDeprecationWarning, + "object.__format__ with a non-empty " + "format string is deprecated", 1) + < 0) { + goto done1; + } + /* Eventually this will become an + error: + PyErr_Format(PyExc_TypeError, + "non-empty format string passed to " + "object.__format__"); + goto done1; + */ + } /* Then call str.__format__ on that result */ format_method = PyObject_GetAttr(self_as_str, str__format__); if (format_method == NULL) { - Py_DECREF(self_as_str); - goto done; + goto done1; } - result = PyObject_CallFunctionObjArgs(format_method, + result = PyObject_CallFunctionObjArgs(format_method, format_spec, NULL); - Py_DECREF(self_as_str); - Py_DECREF(format_method); +done1: + Py_XDECREF(self_as_str); + Py_XDECREF(format_method); if (result == NULL) goto done; - } + } } else { /* Not an instance of a classic class, use the code from py3k */ diff --git a/Objects/typeobject.c b/Objects/typeobject.c index 28df7f29d15..e4176e29bf2 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -3414,31 +3414,54 @@ object_format(PyObject *self, PyObject *args) PyObject *self_as_str = NULL; PyObject *result = NULL; PyObject *format_meth = NULL; + Py_ssize_t format_len; if (!PyArg_ParseTuple(args, "O:__format__", &format_spec)) return NULL; #ifdef Py_USING_UNICODE if (PyUnicode_Check(format_spec)) { + format_len = PyUnicode_GET_SIZE(format_spec); self_as_str = PyObject_Unicode(self); } else if (PyString_Check(format_spec)) { #else if (PyString_Check(format_spec)) { #endif + format_len = PyString_GET_SIZE(format_spec); self_as_str = PyObject_Str(self); } else { - PyErr_SetString(PyExc_TypeError, "argument to __format__ must be unicode or str"); + PyErr_SetString(PyExc_TypeError, + "argument to __format__ must be unicode or str"); return NULL; } if (self_as_str != NULL) { + /* Issue 7994: If we're converting to a string, we + should reject format specifications */ + if (format_len > 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; + */ + } + /* find the format function */ - format_meth = PyObject_GetAttrString(self_as_str, "__format__"); + format_meth = PyObject_GetAttrString(self_as_str, + "__format__"); if (format_meth != NULL) { /* and call it */ - result = PyObject_CallFunctionObjArgs(format_meth, format_spec, NULL); + result = PyObject_CallFunctionObjArgs(format_meth, + format_spec, + NULL); } } +done: Py_XDECREF(self_as_str); Py_XDECREF(format_meth);