[Python-checkins] r71938 - in python/trunk: Doc/c-api/conversion.rst Include/pystrtod.h Lib/test/test_ascii_formatd.py Misc/NEWS Modules/cPickle.c Objects/floatobject.c Objects/stringobject.c Objects/unicodeobject.c Python/pystrtod.c

eric.smith python-checkins at python.org
Sat Apr 25 23:40:15 CEST 2009


Author: eric.smith
Date: Sat Apr 25 23:40:15 2009
New Revision: 71938

Log:
Issue #5835, deprecate PyOS_ascii_formatd.

If anyone wants to clean up the documentation, feel free. It's my first documentation foray, and it's not that great.

Will port to py3k with a different strategy.

Added:
   python/trunk/Lib/test/test_ascii_formatd.py
Modified:
   python/trunk/Doc/c-api/conversion.rst
   python/trunk/Include/pystrtod.h
   python/trunk/Misc/NEWS
   python/trunk/Modules/cPickle.c
   python/trunk/Objects/floatobject.c
   python/trunk/Objects/stringobject.c
   python/trunk/Objects/unicodeobject.c
   python/trunk/Python/pystrtod.c

Modified: python/trunk/Doc/c-api/conversion.rst
==============================================================================
--- python/trunk/Doc/c-api/conversion.rst	(original)
+++ python/trunk/Doc/c-api/conversion.rst	Sat Apr 25 23:40:15 2009
@@ -76,7 +76,42 @@
    the conversion failed.
 
    .. versionadded:: 2.4
+   .. deprecated:: 2.7
+      This function is removed in Python 2.7 and 3.1.  Use :func:`PyOS_double_to_string`
+      instead.
 
+.. cfunction:: char * PyOS_double_to_string(double val, char format_code, int precision, int flags, int *ptype)
+
+   Convert a :ctype:`double` *val* to a string using supplied
+   *format_code*, *precision*, and *flags*.
+
+   *format_code* must be one of ``'e'``, ``'E'``, ``'f'``, ``'F'``,
+   ``'g'``, ``'G'``, ``'s'``, or ``'r'``. For ``'s'`` and ``'r'``, the
+   supplied *precision* must be 0 and is ignored. These specify the
+   standards :func:`str` and :func:`repr` formats, respectively.
+
+   *flags* can be zero or more of the values *Py_DTSF_SIGN*,
+   *Py_DTSF_ADD_DOT_0*, or *Py_DTSF_ALT*, and-ed together.
+
+       *Py_DTSF_SIGN* means always precede the returned string with a
+       sign character, even if *val* is non-negative.
+
+       *Py_DTSF_ADD_DOT_0* means ensure that the returned string will
+       not look like an integer.
+
+       *Py_DTSF_ALT* means apply "alternate" formatting rules. See the
+       documentation for the :func:`PyOS_snprintf` ``'#'`` specifier
+       for details.
+
+   If *ptype* is non-NULL, then the value it points to will be set to
+   one of *Py_DTST_FINITE*, *Py_DTST_INFINITE*, or *Py_DTST_NAN*,
+   signifying that *val* is a finite number, an infinite number, or
+   not a number, respectively.
+
+   The return value is a pointer to *buffer* with the converted string or NULL if
+   the conversion failed.
+
+   .. versionadded:: 2.7
 
 .. cfunction:: double PyOS_ascii_atof(const char *nptr)
 

Modified: python/trunk/Include/pystrtod.h
==============================================================================
--- python/trunk/Include/pystrtod.h	(original)
+++ python/trunk/Include/pystrtod.h	Sat Apr 25 23:40:15 2009
@@ -8,7 +8,17 @@
 
 PyAPI_FUNC(double) PyOS_ascii_strtod(const char *str, char **ptr);
 PyAPI_FUNC(double) PyOS_ascii_atof(const char *str);
-PyAPI_FUNC(char *) PyOS_ascii_formatd(char *buffer, size_t buf_len,  const char *format, double d);
+
+/* Deprecated in 2.7 and 3.1. Will disappear in 2.8 (if it exists) and 3.2 */
+PyAPI_FUNC(char *) PyOS_ascii_formatd(char *buffer, size_t buf_len,
+                                      const char *format, double d);
+
+/* Use PyOS_double_to_string instead. It's the same, except it allocates
+   the appropriately sized buffer and returns it. This function will go
+   away in Python 2.8 and 3.2. */
+PyAPI_FUNC(void) _PyOS_double_to_string(char *buf, size_t buf_len, double val,
+                                        char format_code, int precision,
+                                        int flags, int* type);
 
 /* The caller is responsible for calling PyMem_Free to free the buffer
    that's is returned. */

Added: python/trunk/Lib/test/test_ascii_formatd.py
==============================================================================
--- (empty file)
+++ python/trunk/Lib/test/test_ascii_formatd.py	Sat Apr 25 23:40:15 2009
@@ -0,0 +1,62 @@
+# PyOS_ascii_formatd is deprecated and not called from anywhere in
+#  Python itself. So this module is the only place it gets tested.
+# Test that it works, and test that it's deprecated.
+
+import unittest
+from test_support import check_warnings, run_unittest, cpython_only
+
+
+class FormatDeprecationTests(unittest.TestCase):
+
+    @cpython_only
+    def testFormatDeprecation(self):
+        # delay importing ctypes until we know we're in CPython
+        from ctypes import (pythonapi, create_string_buffer, sizeof, byref,
+                            c_double)
+        PyOS_ascii_formatd = pythonapi.PyOS_ascii_formatd
+        buf = create_string_buffer(' ' * 100)
+
+        with check_warnings() as w:
+            PyOS_ascii_formatd(byref(buf), sizeof(buf), '%+.10f',
+                               c_double(10.0))
+            self.assertEqual(buf.value, '+10.0000000000')
+
+        self.assertEqual(str(w.message), 'PyOS_ascii_formatd is deprecated, '
+                         'use PyOS_double_to_string instead')
+
+class FormatTests(unittest.TestCase):
+    # ensure that, for the restricted set of format codes,
+    # %-formatting returns the same values os PyOS_ascii_formatd
+    @cpython_only
+    def testFormat(self):
+        # delay importing ctypes until we know we're in CPython
+        from ctypes import (pythonapi, create_string_buffer, sizeof, byref,
+                            c_double)
+        PyOS_ascii_formatd = pythonapi.PyOS_ascii_formatd
+        buf = create_string_buffer(' ' * 100)
+
+        tests = [
+            ('%f', 100.0),
+            ('%g', 100.0),
+            ('%#g', 100.0),
+            ('%#.2g', 100.0),
+            ('%#.2g', 123.4567),
+            ('%#.2g', 1.234567e200),
+            ('%e', 1.234567e200),
+            ('%e', 1.234),
+            ('%+e', 1.234),
+            ('%-e', 1.234),
+            ]
+
+        with check_warnings():
+            for format, val in tests:
+                PyOS_ascii_formatd(byref(buf), sizeof(buf), format,
+                                   c_double(val))
+                self.assertEqual(buf.value, format % val)
+
+
+def test_main():
+    run_unittest(FormatDeprecationTests, FormatTests)
+
+if __name__ == '__main__':
+    test_main()

Modified: python/trunk/Misc/NEWS
==============================================================================
--- python/trunk/Misc/NEWS	(original)
+++ python/trunk/Misc/NEWS	Sat Apr 25 23:40:15 2009
@@ -12,6 +12,9 @@
 Core and Builtins
 -----------------
 
+- Issue #5835: Deprecate PyOS_ascii_formatd and replace it with
+  _PyOS_double_to_string or PyOS_double_to_string.
+
 - Issue #5283: Setting __class__ in __del__ caused a segfault.
 
 - Issue #5816: complex(repr(z)) now recovers z exactly, even when

Modified: python/trunk/Modules/cPickle.c
==============================================================================
--- python/trunk/Modules/cPickle.c	(original)
+++ python/trunk/Modules/cPickle.c	Sat Apr 25 23:40:15 2009
@@ -1166,7 +1166,8 @@
 	else {
 		char c_str[250];
 		c_str[0] = FLOAT;
-		PyOS_ascii_formatd(c_str + 1, sizeof(c_str) - 2, "%.17g", x);
+		_PyOS_double_to_string(c_str + 1, sizeof(c_str) - 2, x, 'g',
+                                       17, 0, NULL);
 		/* Extend the formatted string with a newline character */
 		strcat(c_str, "\n");
 

Modified: python/trunk/Objects/floatobject.c
==============================================================================
--- python/trunk/Objects/floatobject.c	(original)
+++ python/trunk/Objects/floatobject.c	Sat Apr 25 23:40:15 2009
@@ -342,7 +342,6 @@
 format_float(char *buf, size_t buflen, PyFloatObject *v, int precision)
 {
 	register char *cp;
-	char format[32];
 	int i;
 
 	/* Subroutine for float_repr and float_print.
@@ -352,8 +351,8 @@
 	   in such cases, we append ".0" to the string. */
 
 	assert(PyFloat_Check(v));
-	PyOS_snprintf(format, 32, "%%.%ig", precision);
-	PyOS_ascii_formatd(buf, buflen, format, v->ob_fval);
+	_PyOS_double_to_string(buf, buflen, v->ob_fval, 'g', precision,
+                               0, NULL);
 	cp = buf;
 	if (*cp == '-')
 		cp++;

Modified: python/trunk/Objects/stringobject.c
==============================================================================
--- python/trunk/Objects/stringobject.c	(original)
+++ python/trunk/Objects/stringobject.c	Sat Apr 25 23:40:15 2009
@@ -4332,9 +4332,6 @@
 formatfloat(char *buf, size_t buflen, int flags,
             int prec, int type, PyObject *v)
 {
-	/* fmt = '%#.' + `prec` + `type`
-	   worst case length = 3 + 10 (len of INT_MAX) + 1 = 14 (use 20)*/
-	char fmt[20];
 	double x;
 	x = PyFloat_AsDouble(v);
 	if (x == -1.0 && PyErr_Occurred()) {
@@ -4378,10 +4375,8 @@
 			"formatted float is too long (precision too large?)");
 		return -1;
 	}
-	PyOS_snprintf(fmt, sizeof(fmt), "%%%s.%d%c",
-		      (flags&F_ALT) ? "#" : "",
-		      prec, type);
-	PyOS_ascii_formatd(buf, buflen, fmt, x);
+	_PyOS_double_to_string(buf, buflen, x, type, prec,
+                            (flags&F_ALT)?Py_DTSF_ALT:0, NULL);
 	return (int)strlen(buf);
 }
 

Modified: python/trunk/Objects/unicodeobject.c
==============================================================================
--- python/trunk/Objects/unicodeobject.c	(original)
+++ python/trunk/Objects/unicodeobject.c	Sat Apr 25 23:40:15 2009
@@ -8245,11 +8245,13 @@
 }
 
 static int
-doubletounicode(Py_UNICODE *buffer, size_t len, const char *format, double x)
+doubletounicode(Py_UNICODE *buffer, size_t len, int format_code,
+                int precision, int flags, double x)
 {
     Py_ssize_t result;
 
-    PyOS_ascii_formatd((char *)buffer, len, format, x);
+    _PyOS_double_to_string((char *)buffer, len, x, format_code, precision,
+                           flags, NULL);
     result = strtounicode(buffer, (char *)buffer);
     return Py_SAFE_DOWNCAST(result, Py_ssize_t, int);
 }
@@ -8276,9 +8278,6 @@
             int type,
             PyObject *v)
 {
-    /* fmt = '%#.' + `prec` + `type`
-       worst case length = 3 + 10 (len of INT_MAX) + 1 = 14 (use 20)*/
-    char fmt[20];
     double x;
 
     x = PyFloat_AsDouble(v);
@@ -8320,10 +8319,8 @@
                         "formatted float is too long (precision too large?)");
         return -1;
     }
-    PyOS_snprintf(fmt, sizeof(fmt), "%%%s.%d%c",
-                  (flags&F_ALT) ? "#" : "",
-                  prec, type);
-    return doubletounicode(buf, buflen, fmt, x);
+    return doubletounicode(buf, buflen, type, prec,
+                           (flags&F_ALT)?Py_DTSF_ALT:0, x);
 }
 
 static PyObject*

Modified: python/trunk/Python/pystrtod.c
==============================================================================
--- python/trunk/Python/pystrtod.c	(original)
+++ python/trunk/Python/pystrtod.c	Sat Apr 25 23:40:15 2009
@@ -236,6 +236,25 @@
 }
 
 
+Py_LOCAL_INLINE(void)
+ensure_sign(char* buffer, size_t buf_size)
+{
+	Py_ssize_t len;
+
+	if (buffer[0] == '-')
+		/* Already have a sign. */
+		return;
+
+	/* Include the trailing 0 byte. */
+	len = strlen(buffer)+1;
+	if (len >= buf_size+1)
+		/* No room for the sign, don't do anything. */
+		return;
+
+	memmove(buffer+1, buffer, len);
+	buffer[0] = '+';
+}
+
 /* From the C99 standard, section 7.19.6:
 The exponent always contains at least two digits, and only as many more digits
 as necessary to represent the exponent.
@@ -363,7 +382,7 @@
 #define FLOAT_FORMATBUFLEN 120
 
 /**
- * PyOS_ascii_formatd:
+ * _PyOS_ascii_formatd:
  * @buffer: A buffer to place the resulting string in
  * @buf_size: The length of the buffer.
  * @format: The printf()-style format to use for the
@@ -380,7 +399,8 @@
  *
  * Return value: The pointer to the buffer with the converted string.
  **/
-char *
+/* DEPRECATED, will be deleted in 2.8 and 3.2 */
+PyAPI_FUNC(char *)
 PyOS_ascii_formatd(char       *buffer, 
 		   size_t      buf_size, 
 		   const char *format, 
@@ -393,6 +413,11 @@
 	   also with at least one character past the decimal. */
 	char tmp_format[FLOAT_FORMATBUFLEN];
 
+	if (PyErr_WarnEx(PyExc_DeprecationWarning,
+			 "PyOS_ascii_formatd is deprecated, "
+			 "use PyOS_double_to_string instead", 1) < 0)
+		return NULL;
+
 	/* The last character in the format string must be the format char */
 	format_char = format[format_len - 1];
 
@@ -456,20 +481,22 @@
 	return buffer;
 }
 
-PyAPI_FUNC(char *) PyOS_double_to_string(double val,
-                                         char format_code,
-                                         int precision,
-                                         int flags,
-                                         int *type)
+PyAPI_FUNC(void)
+_PyOS_double_to_string(char *buf, size_t buf_len, double val,
+		    char format_code, int precision,
+		    int flags, int *ptype)
 {
-	char buf[128];
 	char format[32];
-	Py_ssize_t len;
-	char *result;
-	char *p;
 	int t;
 	int upper = 0;
 
+	if (buf_len < 1) {
+		assert(0);
+		/* There's no way to signal this error. Just return. */
+		return;
+	}
+	buf[0] = 0;
+
 	/* Validate format_code, and map upper and lower case */
 	switch (format_code) {
 	case 'e':          /* exponent */
@@ -490,25 +517,29 @@
 		break;
 	case 'r':          /* repr format */
 		/* Supplied precision is unused, must be 0. */
-		if (precision != 0) {
-			PyErr_BadInternalCall();
-			return NULL;
-		}
+		if (precision != 0)
+			return;
 		precision = 17;
 		format_code = 'g';
 		break;
 	case 's':          /* str format */
 		/* Supplied precision is unused, must be 0. */
-		if (precision != 0) {
-			PyErr_BadInternalCall();
-			return NULL;
-		}
+		if (precision != 0)
+			return;
 		precision = 12;
 		format_code = 'g';
 		break;
 	default:
-		PyErr_BadInternalCall();
-		return NULL;
+		assert(0);
+		return;
+	}
+
+	/* Check for buf too small to fit "-inf". Other buffer too small
+	   conditions are dealt with when converting or formatting finite
+	   numbers. */
+	if (buf_len < 5) {
+		assert(0);
+		return;
 	}
 
 	/* Handle nan and inf. */
@@ -524,41 +555,74 @@
 	} else {
 		t = Py_DTST_FINITE;
 
+		/* Build the format string. */
+		PyOS_snprintf(format, sizeof(format), "%%%s.%i%c",
+			      (flags & Py_DTSF_ALT ? "#" : ""), precision,
+			      format_code);
+
+		/* Have PyOS_snprintf do the hard work. */
+		PyOS_snprintf(buf, buf_len, format, val);
+
+		/* Do various fixups on the return string */
+
+		/* Get the current locale, and find the decimal point string.
+		   Convert that string back to a dot. */
+		change_decimal_from_locale_to_dot(buf);
+
+		/* If an exponent exists, ensure that the exponent is at least
+		   MIN_EXPONENT_DIGITS digits, providing the buffer is large
+		   enough for the extra zeros.  Also, if there are more than
+		   MIN_EXPONENT_DIGITS, remove as many zeros as possible until
+		   we get back to MIN_EXPONENT_DIGITS */
+		ensure_minumim_exponent_length(buf, buf_len);
 
+		/* Possibly make sure we have at least one character after the
+		   decimal point (and make sure we have a decimal point). */
 		if (flags & Py_DTSF_ADD_DOT_0)
-			format_code = 'Z';
+			ensure_decimal_point(buf, buf_len);
+	}
 
-		PyOS_snprintf(format, 32, "%%%s.%i%c", (flags & Py_DTSF_ALT ? "#" : ""), precision, format_code);
-		PyOS_ascii_formatd(buf, sizeof(buf), format, val);
+	/* Add the sign if asked and the result isn't negative. */
+	if (flags & Py_DTSF_SIGN && buf[0] != '-')
+		ensure_sign(buf, buf_len);
+
+	if (upper) {
+		/* Convert to upper case. */
+		char *p;
+		for (p = buf; *p; p++)
+			*p = toupper(*p);
 	}
 
+	if (ptype)
+		*ptype = t;
+}
+
+
+PyAPI_FUNC(char *) PyOS_double_to_string(double val,
+                                         char format_code,
+                                         int precision,
+                                         int flags,
+                                         int *ptype)
+{
+	char buf[128];
+	Py_ssize_t len;
+	char *result;
+
+	_PyOS_double_to_string(buf, sizeof(buf), val, format_code, precision,
+			       flags, ptype);
 	len = strlen(buf);
+	if (len == 0) {
+		PyErr_BadInternalCall();
+		return NULL;
+	}
 
-	/* Add 1 for the trailing 0 byte.
-	   Add 1 because we might need to make room for the sign.
-	   */
-	result = PyMem_Malloc(len + 2);
+	/* Add 1 for the trailing 0 byte. */
+	result = PyMem_Malloc(len + 1);
 	if (result == NULL) {
 		PyErr_NoMemory();
 		return NULL;
 	}
-	p = result;
-
-	/* Add sign when requested.  It's convenient (esp. when formatting
-	 complex numbers) to include a sign even for inf and nan. */
-	if (flags & Py_DTSF_SIGN && buf[0] != '-')
-		*p++ = '+';
-
-	strcpy(p, buf);
-
-	if (upper) {
-		/* Convert to upper case. */
-		char *p1;
-		for (p1 = p; *p1; p1++)
-			*p1 = toupper(*p1);
-	}
+	strcpy(result, buf);
 
-	if (type)
-		*type = t;
 	return result;
 }


More information about the Python-checkins mailing list