Skip to content

Commit e777e55

Browse files
author
jeffrey.yasskin
committed
Make int() and long() fall back to __trunc__(). See issue 2002.
git-svn-id: http://svn.python.org/projects/python/trunk@60566 6015fed2-1504-0410-9fe1-9d1591cc4771
1 parent d9ff961 commit e777e55

5 files changed

Lines changed: 232 additions & 3 deletions

File tree

Include/abstract.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -760,6 +760,19 @@ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx*/
760760

761761
PyAPI_FUNC(Py_ssize_t) PyNumber_AsSsize_t(PyObject *o, PyObject *exc);
762762

763+
/*
764+
Returns the Integral instance converted to an int. The
765+
instance is expected to be int or long or have an __int__
766+
method. Steals integral's reference. error_format will be
767+
used to create the TypeError if integral isn't actually an
768+
Integral instance. error_format should be a format string
769+
that can accept a char* naming integral's type.
770+
*/
771+
772+
PyAPI_FUNC(PyObject *) _PyNumber_ConvertIntegralToInt(
773+
PyObject *integral,
774+
const char* error_format);
775+
763776
/*
764777
Returns the object converted to Py_ssize_t by going through
765778
PyNumber_Index first. If an overflow error occurs while

Lib/rational.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -424,8 +424,6 @@ def __trunc__(a):
424424
else:
425425
return a.numerator // a.denominator
426426

427-
__int__ = __trunc__
428-
429427
def __hash__(self):
430428
"""hash(self)
431429

Lib/test/test_builtin.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,6 +934,14 @@ def test_int(self):
934934

935935
def test_intconversion(self):
936936
# Test __int__()
937+
class ClassicMissingMethods:
938+
pass
939+
self.assertRaises(AttributeError, int, ClassicMissingMethods())
940+
941+
class MissingMethods(object):
942+
pass
943+
self.assertRaises(TypeError, int, MissingMethods())
944+
937945
class Foo0:
938946
def __int__(self):
939947
return 42
@@ -965,6 +973,49 @@ def __int__(self):
965973
self.assertEqual(int(Foo4()), 42L)
966974
self.assertRaises(TypeError, int, Foo5())
967975

976+
class Classic:
977+
pass
978+
for base in (object, Classic):
979+
class IntOverridesTrunc(base):
980+
def __int__(self):
981+
return 42
982+
def __trunc__(self):
983+
return -12
984+
self.assertEqual(int(IntOverridesTrunc()), 42)
985+
986+
class JustTrunc(base):
987+
def __trunc__(self):
988+
return 42
989+
self.assertEqual(int(JustTrunc()), 42)
990+
991+
for trunc_result_base in (object, Classic):
992+
class Integral(trunc_result_base):
993+
def __int__(self):
994+
return 42
995+
996+
class TruncReturnsNonInt(base):
997+
def __trunc__(self):
998+
return Integral()
999+
self.assertEqual(int(TruncReturnsNonInt()), 42)
1000+
1001+
class NonIntegral(trunc_result_base):
1002+
def __trunc__(self):
1003+
# Check that we avoid infinite recursion.
1004+
return NonIntegral()
1005+
1006+
class TruncReturnsNonIntegral(base):
1007+
def __trunc__(self):
1008+
return NonIntegral()
1009+
try:
1010+
int(TruncReturnsNonIntegral())
1011+
except TypeError as e:
1012+
self.assertEquals(str(e),
1013+
"__trunc__ returned non-Integral"
1014+
" (type NonIntegral)")
1015+
else:
1016+
self.fail("Failed to raise TypeError with %s" %
1017+
((base, trunc_result_base),))
1018+
9681019
def test_intern(self):
9691020
self.assertRaises(TypeError, intern)
9701021
s = "never interned before"
@@ -1207,6 +1258,14 @@ def test_long(self):
12071258

12081259
def test_longconversion(self):
12091260
# Test __long__()
1261+
class ClassicMissingMethods:
1262+
pass
1263+
self.assertRaises(AttributeError, long, ClassicMissingMethods())
1264+
1265+
class MissingMethods(object):
1266+
pass
1267+
self.assertRaises(TypeError, long, MissingMethods())
1268+
12101269
class Foo0:
12111270
def __long__(self):
12121271
return 42L
@@ -1238,6 +1297,49 @@ def __long__(self):
12381297
self.assertEqual(long(Foo4()), 42)
12391298
self.assertRaises(TypeError, long, Foo5())
12401299

1300+
class Classic:
1301+
pass
1302+
for base in (object, Classic):
1303+
class LongOverridesTrunc(base):
1304+
def __long__(self):
1305+
return 42
1306+
def __trunc__(self):
1307+
return -12
1308+
self.assertEqual(long(LongOverridesTrunc()), 42)
1309+
1310+
class JustTrunc(base):
1311+
def __trunc__(self):
1312+
return 42
1313+
self.assertEqual(long(JustTrunc()), 42)
1314+
1315+
for trunc_result_base in (object, Classic):
1316+
class Integral(trunc_result_base):
1317+
def __int__(self):
1318+
return 42
1319+
1320+
class TruncReturnsNonLong(base):
1321+
def __trunc__(self):
1322+
return Integral()
1323+
self.assertEqual(long(TruncReturnsNonLong()), 42)
1324+
1325+
class NonIntegral(trunc_result_base):
1326+
def __trunc__(self):
1327+
# Check that we avoid infinite recursion.
1328+
return NonIntegral()
1329+
1330+
class TruncReturnsNonIntegral(base):
1331+
def __trunc__(self):
1332+
return NonIntegral()
1333+
try:
1334+
long(TruncReturnsNonIntegral())
1335+
except TypeError as e:
1336+
self.assertEquals(str(e),
1337+
"__trunc__ returned non-Integral"
1338+
" (type NonIntegral)")
1339+
else:
1340+
self.fail("Failed to raise TypeError with %s" %
1341+
((base, trunc_result_base),))
1342+
12411343
def test_map(self):
12421344
self.assertEqual(
12431345
map(None, 'hello world'),

Objects/abstract.c

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1034,13 +1034,65 @@ PyNumber_AsSsize_t(PyObject *item, PyObject *err)
10341034
}
10351035

10361036

1037+
PyObject *
1038+
_PyNumber_ConvertIntegralToInt(PyObject *integral, const char* error_format)
1039+
{
1040+
const char *type_name;
1041+
static PyObject *int_name = NULL;
1042+
if (int_name == NULL) {
1043+
int_name = PyString_InternFromString("__int__");
1044+
if (int_name == NULL)
1045+
return NULL;
1046+
}
1047+
1048+
if (integral && (!PyInt_Check(integral) &&
1049+
!PyLong_Check(integral))) {
1050+
/* Don't go through tp_as_number->nb_int to avoid
1051+
hitting the classic class fallback to __trunc__. */
1052+
PyObject *int_func = PyObject_GetAttr(integral, int_name);
1053+
if (int_func == NULL) {
1054+
PyErr_Clear(); /* Raise a different error. */
1055+
goto non_integral_error;
1056+
}
1057+
Py_DECREF(integral);
1058+
integral = PyEval_CallObject(int_func, NULL);
1059+
Py_DECREF(int_func);
1060+
if (integral && (!PyInt_Check(integral) &&
1061+
!PyLong_Check(integral))) {
1062+
goto non_integral_error;
1063+
}
1064+
}
1065+
return integral;
1066+
1067+
non_integral_error:
1068+
if (PyInstance_Check(integral)) {
1069+
type_name = PyString_AS_STRING(((PyInstanceObject *)integral)
1070+
->in_class->cl_name);
1071+
}
1072+
else {
1073+
type_name = integral->ob_type->tp_name;
1074+
}
1075+
PyErr_Format(PyExc_TypeError, error_format, type_name);
1076+
Py_DECREF(integral);
1077+
return NULL;
1078+
}
1079+
1080+
10371081
PyObject *
10381082
PyNumber_Int(PyObject *o)
10391083
{
10401084
PyNumberMethods *m;
1085+
static PyObject *trunc_name = NULL;
1086+
PyObject *trunc_func;
10411087
const char *buffer;
10421088
Py_ssize_t buffer_len;
10431089

1090+
if (trunc_name == NULL) {
1091+
trunc_name = PyString_InternFromString("__trunc__");
1092+
if (trunc_name == NULL)
1093+
return NULL;
1094+
}
1095+
10441096
if (o == NULL)
10451097
return null_error();
10461098
if (PyInt_CheckExact(o)) {
@@ -1049,6 +1101,7 @@ PyNumber_Int(PyObject *o)
10491101
}
10501102
m = o->ob_type->tp_as_number;
10511103
if (m && m->nb_int) { /* This should include subclasses of int */
1104+
/* Classic classes always take this branch. */
10521105
PyObject *res = m->nb_int(o);
10531106
if (res && (!PyInt_Check(res) && !PyLong_Check(res))) {
10541107
PyErr_Format(PyExc_TypeError,
@@ -1063,6 +1116,18 @@ PyNumber_Int(PyObject *o)
10631116
PyIntObject *io = (PyIntObject*)o;
10641117
return PyInt_FromLong(io->ob_ival);
10651118
}
1119+
trunc_func = PyObject_GetAttr(o, trunc_name);
1120+
if (trunc_func) {
1121+
PyObject *truncated = PyEval_CallObject(trunc_func, NULL);
1122+
Py_DECREF(trunc_func);
1123+
/* __trunc__ is specified to return an Integral type, but
1124+
int() needs to return an int. */
1125+
return _PyNumber_ConvertIntegralToInt(
1126+
truncated,
1127+
"__trunc__ returned non-Integral (type %.200s)");
1128+
}
1129+
PyErr_Clear(); /* It's not an error if o.__trunc__ doesn't exist. */
1130+
10661131
if (PyString_Check(o))
10671132
return int_from_string(PyString_AS_STRING(o),
10681133
PyString_GET_SIZE(o));
@@ -1102,13 +1167,22 @@ PyObject *
11021167
PyNumber_Long(PyObject *o)
11031168
{
11041169
PyNumberMethods *m;
1170+
static PyObject *trunc_name = NULL;
1171+
PyObject *trunc_func;
11051172
const char *buffer;
11061173
Py_ssize_t buffer_len;
11071174

1175+
if (trunc_name == NULL) {
1176+
trunc_name = PyString_InternFromString("__trunc__");
1177+
if (trunc_name == NULL)
1178+
return NULL;
1179+
}
1180+
11081181
if (o == NULL)
11091182
return null_error();
11101183
m = o->ob_type->tp_as_number;
11111184
if (m && m->nb_long) { /* This should include subclasses of long */
1185+
/* Classic classes always take this branch. */
11121186
PyObject *res = m->nb_long(o);
11131187
if (res && (!PyInt_Check(res) && !PyLong_Check(res))) {
11141188
PyErr_Format(PyExc_TypeError,
@@ -1121,6 +1195,26 @@ PyNumber_Long(PyObject *o)
11211195
}
11221196
if (PyLong_Check(o)) /* A long subclass without nb_long */
11231197
return _PyLong_Copy((PyLongObject *)o);
1198+
trunc_func = PyObject_GetAttr(o, trunc_name);
1199+
if (trunc_func) {
1200+
PyObject *truncated = PyEval_CallObject(trunc_func, NULL);
1201+
PyObject *int_instance;
1202+
Py_DECREF(trunc_func);
1203+
/* __trunc__ is specified to return an Integral type,
1204+
but long() needs to return a long. */
1205+
int_instance = _PyNumber_ConvertIntegralToInt(
1206+
truncated,
1207+
"__trunc__ returned non-Integral (type %.200s)");
1208+
if (int_instance && PyInt_Check(int_instance)) {
1209+
/* Make sure that long() returns a long instance. */
1210+
long value = PyInt_AS_LONG(int_instance);
1211+
Py_DECREF(int_instance);
1212+
return PyLong_FromLong(value);
1213+
}
1214+
return int_instance;
1215+
}
1216+
PyErr_Clear(); /* It's not an error if o.__trunc__ doesn't exist. */
1217+
11241218
if (PyString_Check(o))
11251219
/* need to do extra error checking that PyLong_FromString()
11261220
* doesn't do. In particular long('9.5') must raise an

Objects/classobject.c

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1798,7 +1798,29 @@ instance_index(PyInstanceObject *self)
17981798

17991799

18001800
UNARY(instance_invert, "__invert__")
1801-
UNARY(instance_int, "__int__")
1801+
UNARY(_instance_trunc, "__trunc__")
1802+
1803+
static PyObject *
1804+
instance_int(PyInstanceObject *self)
1805+
{
1806+
PyObject *truncated;
1807+
static PyObject *int_name;
1808+
if (int_name == NULL) {
1809+
int_name = PyString_InternFromString("__int__");
1810+
if (int_name == NULL)
1811+
return NULL;
1812+
}
1813+
if (PyObject_HasAttr((PyObject*)self, int_name))
1814+
return generic_unary_op(self, int_name);
1815+
1816+
truncated = _instance_trunc(self);
1817+
/* __trunc__ is specified to return an Integral type, but
1818+
int() needs to return an int. */
1819+
return _PyNumber_ConvertIntegralToInt(
1820+
truncated,
1821+
"__trunc__ returned non-Integral (type %.200s)");
1822+
}
1823+
18021824
UNARY_FB(instance_long, "__long__", instance_int)
18031825
UNARY(instance_float, "__float__")
18041826
UNARY(instance_oct, "__oct__")

0 commit comments

Comments
 (0)