]> granicus.if.org Git - python/commitdiff
Merged revisions 80758 via svnmerge from
authorMark Dickinson <dickinsm@gmail.com>
Fri, 7 May 2010 13:23:18 +0000 (13:23 +0000)
committerMark Dickinson <dickinsm@gmail.com>
Fri, 7 May 2010 13:23:18 +0000 (13:23 +0000)
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).
........

Lib/test/test_builtin.py
Misc/NEWS
Python/bltinmodule.c

index 04c2216765444170a06b4a570e2f3c0a212a0d41..d1d2bd00e015097247c77341d698900a3ced71c0 100644 (file)
@@ -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 @@ class BuiltinTest(unittest.TestCase):
         # 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 @@ class BuiltinTest(unittest.TestCase):
         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')
index 8e33eb9e6ebf92b62f9b11d4154371209eeb0d44..944f804ec7df1c44c676b3b0ed821363cd6efba5 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -12,6 +12,12 @@ What's New in Python 2.6.6 alpha 1?
 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.
 
index 4b5a062f6ff3fc0b3c25afe9ad9f3fa296555e2a..08cc8b368d7f5a2ccfe0dad83feb725f2132fa77 100644 (file)
@@ -1745,15 +1745,54 @@ get_len_of_range_longs(PyObject *lo, PyObject *hi, PyObject *step)
        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 @@ handle_range_longs(PyObject *self, PyObject *args)
 
        /* 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 @@ handle_range_longs(PyObject *self, PyObject *args)
                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 @@ handle_range_longs(PyObject *self, PyObject *args)
        }
 
        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 @@ handle_range_longs(PyObject *self, PyObject *args)
        if (v == NULL)
                goto Fail;
 
-       curnum = ilow;
+       curnum = low;
        Py_INCREF(curnum);
 
        for (i = 0; i < n; i++) {
@@ -1859,24 +1886,24 @@ handle_range_longs(PyObject *self, PyObject *args)
 
                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);