[Python-checkins] r80917 - in python/branches/release26-maint: Lib/test/test_builtin.py Misc/NEWS Python/bltinmodule.c

mark.dickinson python-checkins at python.org
Fri May 7 15:23:18 CEST 2010


Author: mark.dickinson
Date: Fri May  7 15:23:18 2010
New Revision: 80917

Log:
Merged revisions 80758 via svnmerge from 
svn+ssh://pythondev@svn.python.org/python/trunk

........
  r80758 | mark.dickinson | 2010-05-04 17:18:25 +0100 (Tue, 04 May 2010) | 9 lines
  
  Issue #1533: fix inconsistency in range function argument processing:
  any non-float non-integer argument is now converted to an integer (if
  possible) using its __int__ method.  Previously, only small arguments
  were treated this way; larger arguments (those whose __int__ was
  outside the range of a C long) would produce a TypeError.
  
  Patch by Alexander Belopolsky (with minor modifications).
........


Modified:
   python/branches/release26-maint/Lib/test/test_builtin.py
   python/branches/release26-maint/Misc/NEWS
   python/branches/release26-maint/Python/bltinmodule.c

Modified: python/branches/release26-maint/Lib/test/test_builtin.py
==============================================================================
--- python/branches/release26-maint/Lib/test/test_builtin.py	(original)
+++ python/branches/release26-maint/Lib/test/test_builtin.py	Fri May  7 15:23:18 2010
@@ -3,7 +3,7 @@
 import platform
 import test.test_support, unittest
 from test.test_support import fcmp, have_unicode, TESTFN, unlink, \
-                              run_unittest, run_with_locale
+    run_unittest, run_with_locale, check_warnings
 from operator import neg
 
 import sys, warnings, cStringIO, random, fractions, UserDict
@@ -1074,6 +1074,10 @@
         # Reject floats when it would require PyLongs to represent.
         # (smaller floats still accepted, but deprecated)
         self.assertRaises(TypeError, range, 1e100, 1e101, 1e101)
+        with check_warnings() as w:
+            warnings.simplefilter("always")
+            self.assertEqual(range(1.0), [0])
+            self.assertEqual(w.category, DeprecationWarning)
 
         self.assertRaises(TypeError, range, 0, "spam")
         self.assertRaises(TypeError, range, 0, 42, "spam")
@@ -1081,6 +1085,54 @@
         self.assertRaises(OverflowError, range, -sys.maxint, sys.maxint)
         self.assertRaises(OverflowError, range, 0, 2*sys.maxint)
 
+        bignum = 2*sys.maxint
+        smallnum = 42
+        # Old-style user-defined class with __int__ method
+        class I0:
+            def __init__(self, n):
+                self.n = int(n)
+            def __int__(self):
+                return self.n
+        self.assertEqual(range(I0(bignum), I0(bignum + 1)), [bignum])
+        self.assertEqual(range(I0(smallnum), I0(smallnum + 1)), [smallnum])
+
+        # New-style user-defined class with __int__ method
+        class I1(object):
+            def __init__(self, n):
+                self.n = int(n)
+            def __int__(self):
+                return self.n
+        self.assertEqual(range(I1(bignum), I1(bignum + 1)), [bignum])
+        self.assertEqual(range(I1(smallnum), I1(smallnum + 1)), [smallnum])
+
+        # New-style user-defined class with failing __int__ method
+        class IX(object):
+            def __int__(self):
+                raise RuntimeError
+        self.assertRaises(RuntimeError, range, IX())
+
+        # New-style user-defined class with invalid __int__ method
+        class IN(object):
+            def __int__(self):
+                return "not a number"
+        self.assertRaises(TypeError, range, IN())
+
+        # Exercise various combinations of bad arguments, to check
+        # refcounting logic
+        self.assertRaises(TypeError, range, 1e100)
+
+        self.assertRaises(TypeError, range, 0, 1e100)
+        self.assertRaises(TypeError, range, 1e100, 0)
+        self.assertRaises(TypeError, range, 1e100, 1e100)
+
+        self.assertRaises(TypeError, range, 0, 0, 1e100)
+        self.assertRaises(TypeError, range, 0, 1e100, 1)
+        self.assertRaises(TypeError, range, 0, 1e100, 1e100)
+        self.assertRaises(TypeError, range, 1e100, 0, 1)
+        self.assertRaises(TypeError, range, 1e100, 0, 1e100)
+        self.assertRaises(TypeError, range, 1e100, 1e100, 1)
+        self.assertRaises(TypeError, range, 1e100, 1e100, 1e100)
+
     def test_input_and_raw_input(self):
         self.write_testfile()
         fp = open(TESTFN, 'r')

Modified: python/branches/release26-maint/Misc/NEWS
==============================================================================
--- python/branches/release26-maint/Misc/NEWS	(original)
+++ python/branches/release26-maint/Misc/NEWS	Fri May  7 15:23:18 2010
@@ -12,6 +12,12 @@
 Core and Builtins
 -----------------
 
+- Issue #1533: fix inconsistency in range function argument
+  processing: any non-float non-integer argument is now converted to
+  an integer (if possible) using its __int__ method.  Previously, only
+  small arguments were treated this way; larger arguments (those whose
+  __int__ was outside the range of a C long) would produce a TypeError.
+
 - Issue #8417: Raise an OverflowError when an integer larger than sys.maxsize is
   passed to bytearray.
 

Modified: python/branches/release26-maint/Python/bltinmodule.c
==============================================================================
--- python/branches/release26-maint/Python/bltinmodule.c	(original)
+++ python/branches/release26-maint/Python/bltinmodule.c	Fri May  7 15:23:18 2010
@@ -1745,15 +1745,54 @@
 	return -1;
 }
 
+/* Helper function for handle_range_longs.  If arg is int or long
+   object, returns it with incremented reference count.  If arg is
+   float, raises type error. As a last resort, creates a new int by
+   calling arg type's nb_int method if it is defined.  Returns NULL
+   and sets exception on error.
+
+   Returns a new reference to an int object. */
+static PyObject *
+get_range_long_argument(PyObject *arg, const char *name)
+{
+	PyObject *v;
+	PyNumberMethods *nb;
+	if (PyInt_Check(arg) || PyLong_Check(arg)) {
+		Py_INCREF(arg);
+		return arg;
+	}
+	if (PyFloat_Check(arg) ||
+	    (nb = Py_TYPE(arg)->tp_as_number) == NULL ||
+	    nb->nb_int == NULL) {
+		PyErr_Format(PyExc_TypeError,
+			     "range() integer %s argument expected, got %s.",
+			     name, arg->ob_type->tp_name);
+		return NULL;
+	}
+	v = nb->nb_int(arg);
+	if (v == NULL)
+		return NULL;
+	if (PyInt_Check(v) || PyLong_Check(v))
+		return v;
+	Py_DECREF(v);
+	PyErr_SetString(PyExc_TypeError,
+			"__int__ should return int object");
+	return NULL;
+}
+
 /* An extension of builtin_range() that handles the case when PyLong
  * arguments are given. */
 static PyObject *
 handle_range_longs(PyObject *self, PyObject *args)
 {
-	PyObject *ilow;
+	PyObject *ilow = NULL;
 	PyObject *ihigh = NULL;
 	PyObject *istep = NULL;
 
+	PyObject *low = NULL;
+	PyObject *high = NULL;
+	PyObject *step = NULL;
+
 	PyObject *curnum = NULL;
 	PyObject *v = NULL;
 	long bign;
@@ -1772,7 +1811,7 @@
 
 	/* Figure out which way we were called, supply defaults, and be
 	 * sure to incref everything so that the decrefs at the end
-	 * are correct.
+	 * are correct. NB: ilow, ihigh and istep are borrowed references.
 	 */
 	assert(ilow != NULL);
 	if (ihigh == NULL) {
@@ -1780,47 +1819,35 @@
 		ihigh = ilow;
 		ilow = NULL;
 	}
+
+	/* convert ihigh if necessary */
 	assert(ihigh != NULL);
-	Py_INCREF(ihigh);
+	high = get_range_long_argument(ihigh, "end");
+	if (high == NULL)
+		goto Fail;
 
 	/* ihigh correct now; do ilow */
-	if (ilow == NULL)
-		ilow = zero;
-	Py_INCREF(ilow);
-
-	/* ilow and ihigh correct now; do istep */
-	if (istep == NULL) {
-		istep = PyLong_FromLong(1L);
-		if (istep == NULL)
-			goto Fail;
+	if (ilow == NULL) {
+		Py_INCREF(zero);
+		low = zero;
 	}
 	else {
-		Py_INCREF(istep);
-	}
-
-	if (!PyInt_Check(ilow) && !PyLong_Check(ilow)) {
-		PyErr_Format(PyExc_TypeError,
-			     "range() integer start argument expected, got %s.",
-			     ilow->ob_type->tp_name);
-		goto Fail;
+		low = get_range_long_argument(ilow, "start");
+		if (low == NULL)
+			goto Fail;
 	}
 
-	if (!PyInt_Check(ihigh) && !PyLong_Check(ihigh)) {
-		PyErr_Format(PyExc_TypeError,
-			     "range() integer end argument expected, got %s.",
-			     ihigh->ob_type->tp_name);
+	/* ilow and ihigh correct now; do istep */
+	if (istep == NULL)
+		step = PyLong_FromLong(1);
+	else
+		step = get_range_long_argument(istep, "step");
+	if (step == NULL)
 		goto Fail;
-	}
 
-	if (!PyInt_Check(istep) && !PyLong_Check(istep)) {
-		PyErr_Format(PyExc_TypeError,
-			     "range() integer step argument expected, got %s.",
-			     istep->ob_type->tp_name);
+	if (PyObject_Cmp(step, zero, &cmp_result) == -1)
 		goto Fail;
-	}
 
-	if (PyObject_Cmp(istep, zero, &cmp_result) == -1)
-		goto Fail;
 	if (cmp_result == 0) {
 		PyErr_SetString(PyExc_ValueError,
 				"range() step argument must not be zero");
@@ -1828,13 +1855,13 @@
 	}
 
 	if (cmp_result > 0)
-		bign = get_len_of_range_longs(ilow, ihigh, istep);
+		bign = get_len_of_range_longs(low, high, step);
 	else {
-		PyObject *neg_istep = PyNumber_Negative(istep);
-		if (neg_istep == NULL)
+		PyObject *neg_step = PyNumber_Negative(step);
+		if (neg_step == NULL)
 			goto Fail;
-		bign = get_len_of_range_longs(ihigh, ilow, neg_istep);
-		Py_DECREF(neg_istep);
+		bign = get_len_of_range_longs(high, low, neg_step);
+		Py_DECREF(neg_step);
 	}
 
 	n = (int)bign;
@@ -1848,7 +1875,7 @@
 	if (v == NULL)
 		goto Fail;
 
-	curnum = ilow;
+	curnum = low;
 	Py_INCREF(curnum);
 
 	for (i = 0; i < n; i++) {
@@ -1859,24 +1886,24 @@
 
 		PyList_SET_ITEM(v, i, w);
 
-		tmp_num = PyNumber_Add(curnum, istep);
+		tmp_num = PyNumber_Add(curnum, step);
 		if (tmp_num == NULL)
 			goto Fail;
 
 		Py_DECREF(curnum);
 		curnum = tmp_num;
 	}
-	Py_DECREF(ilow);
-	Py_DECREF(ihigh);
-	Py_DECREF(istep);
+	Py_DECREF(low);
+	Py_DECREF(high);
+	Py_DECREF(step);
 	Py_DECREF(zero);
 	Py_DECREF(curnum);
 	return v;
 
   Fail:
-	Py_DECREF(ilow);
-	Py_DECREF(ihigh);
-	Py_XDECREF(istep);
+	Py_XDECREF(low);
+	Py_XDECREF(high);
+	Py_XDECREF(step);
 	Py_DECREF(zero);
 	Py_XDECREF(curnum);
 	Py_XDECREF(v);


More information about the Python-checkins mailing list