Index: Include/abstract.h =================================================================== --- Include/abstract.h (revision 60539) +++ Include/abstract.h (working copy) @@ -761,6 +761,19 @@ PyAPI_FUNC(Py_ssize_t) PyNumber_AsSsize_t(PyObject *o, PyObject *exc); /* + Returns the Integral instance converted to an int. The + instance is expected to be int or long or have an __int__ + method. Steals integral's reference. error_format will be + used to create the TypeError if integral isn't actually an + Integral instance. error_format should be a format string + that can accept a char* naming integral's type. + */ + + PyAPI_FUNC(PyObject *) _PyNumber_ConvertIntegralToInt( + PyObject *integral, + const char* error_format); + + /* Returns the object converted to Py_ssize_t by going through PyNumber_Index first. If an overflow error occurs while converting the int-or-long to Py_ssize_t, then the second argument Index: Objects/abstract.c =================================================================== --- Objects/abstract.c (revision 60539) +++ Objects/abstract.c (working copy) @@ -1035,12 +1035,64 @@ PyObject * +_PyNumber_ConvertIntegralToInt(PyObject *integral, const char* error_format) +{ + const char *type_name; + static PyObject *int_name = NULL; + if (int_name == NULL) { + int_name = PyString_InternFromString("__int__"); + if (int_name == NULL) + return NULL; + } + + if (integral && (!PyInt_Check(integral) && + !PyLong_Check(integral))) { + /* Don't go through tp_as_number->nb_int to avoid + hitting the classic class fallback to __trunc__. */ + PyObject *int_func = PyObject_GetAttr(integral, int_name); + if (int_func == NULL) { + PyErr_Clear(); /* Raise a different error. */ + goto non_integral_error; + } + Py_DECREF(integral); + integral = PyEval_CallObject(int_func, NULL); + Py_DECREF(int_func); + if (integral && (!PyInt_Check(integral) && + !PyLong_Check(integral))) { + goto non_integral_error; + } + } + return integral; + +non_integral_error: + if (PyInstance_Check(integral)) { + type_name = PyString_AS_STRING(((PyInstanceObject *)integral) + ->in_class->cl_name); + } + else { + type_name = integral->ob_type->tp_name; + } + PyErr_Format(PyExc_TypeError, error_format, type_name); + Py_DECREF(integral); + return NULL; +} + + +PyObject * PyNumber_Int(PyObject *o) { PyNumberMethods *m; + static PyObject *trunc_name = NULL; + PyObject *trunc_func; const char *buffer; Py_ssize_t buffer_len; + if (trunc_name == NULL) { + trunc_name = PyString_InternFromString("__trunc__"); + if (trunc_name == NULL) + return NULL; + } + if (o == NULL) return null_error(); if (PyInt_CheckExact(o)) { @@ -1063,6 +1115,18 @@ PyIntObject *io = (PyIntObject*)o; return PyInt_FromLong(io->ob_ival); } + trunc_func = PyObject_GetAttr(o, trunc_name); + if (trunc_func) { + PyObject *truncated = PyEval_CallObject(trunc_func, NULL); + Py_DECREF(trunc_func); + /* __trunc__ is specified to return an Integral type, but + int() needs to return an int. */ + return _PyNumber_ConvertIntegralToInt( + truncated, + "__trunc__ returned non-Integral (type %.200s)"); + } + PyErr_Clear(); /* It's not an error if o.__trunc__ doesn't exist. */ + if (PyString_Check(o)) return int_from_string(PyString_AS_STRING(o), PyString_GET_SIZE(o)); @@ -1102,9 +1166,17 @@ PyNumber_Long(PyObject *o) { PyNumberMethods *m; + static PyObject *trunc_name = NULL; + PyObject *trunc_func; const char *buffer; Py_ssize_t buffer_len; + if (trunc_name == NULL) { + trunc_name = PyString_InternFromString("__trunc__"); + if (trunc_name == NULL) + return NULL; + } + if (o == NULL) return null_error(); m = o->ob_type->tp_as_number; @@ -1121,6 +1193,26 @@ } if (PyLong_Check(o)) /* A long subclass without nb_long */ return _PyLong_Copy((PyLongObject *)o); + trunc_func = PyObject_GetAttr(o, trunc_name); + if (trunc_func) { + PyObject *truncated = PyEval_CallObject(trunc_func, NULL); + PyObject *int_instance; + Py_DECREF(trunc_func); + /* __trunc__ is specified to return an Integral type, + but long() needs to return a long. */ + int_instance = _PyNumber_ConvertIntegralToInt( + truncated, + "__trunc__ returned non-Integral (type %.200s)"); + if (int_instance && PyInt_Check(int_instance)) { + /* Make sure that long() returns a long instance. */ + long value = PyInt_AS_LONG(int_instance); + Py_DECREF(int_instance); + return PyLong_FromLong(value); + } + return int_instance; + } + PyErr_Clear(); /* It's not an error if o.__trunc__ doesn't exist. */ + if (PyString_Check(o)) /* need to do extra error checking that PyLong_FromString() * doesn't do. In particular long('9.5') must raise an Index: Objects/classobject.c =================================================================== --- Objects/classobject.c (revision 60539) +++ Objects/classobject.c (working copy) @@ -1798,7 +1798,29 @@ UNARY(instance_invert, "__invert__") -UNARY(instance_int, "__int__") +UNARY(_instance_trunc, "__trunc__") + +static PyObject * +instance_int(PyInstanceObject *self) +{ + PyObject *truncated; + static PyObject *int_name; + if (int_name == NULL) { + int_name = PyString_InternFromString("__int__"); + if (int_name == NULL) + return NULL; + } + if (PyObject_HasAttr((PyObject*)self, int_name)) + return generic_unary_op(self, int_name); + + truncated = _instance_trunc(self); + /* __trunc__ is specified to return an Integral type, but + int() needs to return an int. */ + return _PyNumber_ConvertIntegralToInt( + truncated, + "__trunc__ returned non-Integral (type %.200s)"); +} + UNARY_FB(instance_long, "__long__", instance_int) UNARY(instance_float, "__float__") UNARY(instance_oct, "__oct__") Index: Lib/rational.py =================================================================== --- Lib/rational.py (revision 60539) +++ Lib/rational.py (working copy) @@ -424,8 +424,6 @@ else: return a.numerator // a.denominator - __int__ = __trunc__ - def __hash__(self): """hash(self) Index: Lib/test/test_builtin.py =================================================================== --- Lib/test/test_builtin.py (revision 60539) +++ Lib/test/test_builtin.py (working copy) @@ -934,6 +934,14 @@ def test_intconversion(self): # Test __int__() + class ClassicMissingMethods: + pass + self.assertRaises(AttributeError, int, ClassicMissingMethods()) + + class MissingMethods(object): + pass + self.assertRaises(TypeError, int, MissingMethods()) + class Foo0: def __int__(self): return 42 @@ -965,6 +973,49 @@ self.assertEqual(int(Foo4()), 42L) self.assertRaises(TypeError, int, Foo5()) + class Classic: + pass + for base in (object, Classic): + class IntOverridesTrunc(base): + def __int__(self): + return 42 + def __trunc__(self): + return -12 + self.assertEqual(int(IntOverridesTrunc()), 42) + + class JustTrunc(base): + def __trunc__(self): + return 42 + self.assertEqual(int(JustTrunc()), 42) + + for trunc_result_base in (object, Classic): + class Integral(trunc_result_base): + def __int__(self): + return 42 + + class TruncReturnsNonInt(base): + def __trunc__(self): + return Integral() + self.assertEqual(int(TruncReturnsNonInt()), 42) + + class NonIntegral(trunc_result_base): + def __trunc__(self): + # Check that we avoid infinite recursion. + return NonIntegral() + + class TruncReturnsNonIntegral(base): + def __trunc__(self): + return NonIntegral() + try: + int(TruncReturnsNonIntegral()) + except TypeError as e: + self.assertEquals(str(e), + "__trunc__ returned non-Integral" + " (type NonIntegral)") + else: + self.fail("Failed to raise TypeError with %s" % + ((base, trunc_result_base),)) + def test_intern(self): self.assertRaises(TypeError, intern) s = "never interned before" @@ -1207,6 +1258,14 @@ def test_longconversion(self): # Test __long__() + class ClassicMissingMethods: + pass + self.assertRaises(AttributeError, long, ClassicMissingMethods()) + + class MissingMethods(object): + pass + self.assertRaises(TypeError, long, MissingMethods()) + class Foo0: def __long__(self): return 42L @@ -1238,6 +1297,49 @@ self.assertEqual(long(Foo4()), 42) self.assertRaises(TypeError, long, Foo5()) + class Classic: + pass + for base in (object, Classic): + class LongOverridesTrunc(base): + def __long__(self): + return 42 + def __trunc__(self): + return -12 + self.assertEqual(long(LongOverridesTrunc()), 42) + + class JustTrunc(base): + def __trunc__(self): + return 42 + self.assertEqual(long(JustTrunc()), 42) + + for trunc_result_base in (object, Classic): + class Integral(trunc_result_base): + def __int__(self): + return 42 + + class TruncReturnsNonLong(base): + def __trunc__(self): + return Integral() + self.assertEqual(long(TruncReturnsNonLong()), 42) + + class NonIntegral(trunc_result_base): + def __trunc__(self): + # Check that we avoid infinite recursion. + return NonIntegral() + + class TruncReturnsNonIntegral(base): + def __trunc__(self): + return NonIntegral() + try: + long(TruncReturnsNonIntegral()) + except TypeError as e: + self.assertEquals(str(e), + "__trunc__ returned non-Integral" + " (type NonIntegral)") + else: + self.fail("Failed to raise TypeError with %s" % + ((base, trunc_result_base),)) + def test_map(self): self.assertEqual( map(None, 'hello world'),