]> granicus.if.org Git - python/commitdiff
Issue 7994: Make object.__format__ with a non-empty format string a PendingDecprecati...
authorEric Smith <eric@trueblade.com>
Fri, 2 Apr 2010 12:30:56 +0000 (12:30 +0000)
committerEric Smith <eric@trueblade.com>
Fri, 2 Apr 2010 12:30:56 +0000 (12:30 +0000)
Lib/test/test_builtin.py
Misc/NEWS
Objects/abstract.c
Objects/typeobject.c

index 05686b91198ab3829d348bd2667f8c663f1a0880..013731ceacddf31ac31211c0ac8ceb07d4d8a137 100644 (file)
@@ -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')
index b91bd74ed38e5612e575c749555065600808b00c..cd6f4abcece3abb68097b086b19252e352f36edb 100644 (file)
--- 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.
 
index 8d6f023c47b11e43111ed395f1ed9f0496f44c88..0b541ee74887a57d9a70c719a113297faf3f3c72 100644 (file)
@@ -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 */
index 28df7f29d156164985ad1b0a679e92357b74fbe8..e4176e29bf2383588726a83d8ecedf5c4b22697a 100644 (file)
@@ -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);