Commit c3647ac9 authored by Brett Cannon's avatar Brett Cannon

Make subclasses of int, long, complex, float, and unicode perform type

conversion using the proper magic slot (e.g., __int__()).  Also move conversion
code out of PyNumber_*() functions in the C API into the nb_* function.

Applied patch #1109424.  Thanks Walter Doewald.
parent d7c795e7
......@@ -545,6 +545,37 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(float(unicode(" 3.14 ")), 3.14)
self.assertEqual(float(unicode(" \u0663.\u0661\u0664 ",'raw-unicode-escape')), 3.14)
def test_floatconversion(self):
# Make sure that calls to __float__() work properly
class Foo0:
def __float__(self):
return 42.
class Foo1(object):
def __float__(self):
return 42.
class Foo2(float):
def __float__(self):
return 42.
class Foo3(float):
def __new__(cls, value=0.):
return float.__new__(cls, 2*value)
def __float__(self):
return self
class Foo4(float):
def __float__(self):
return 42
self.assertAlmostEqual(float(Foo0()), 42.)
self.assertAlmostEqual(float(Foo1()), 42.)
self.assertAlmostEqual(float(Foo2()), 42.)
self.assertAlmostEqual(float(Foo3(21)), 42.)
self.assertRaises(TypeError, float, Foo4(42))
def test_getattr(self):
import sys
self.assert_(getattr(sys, 'stdout') is sys.stdout)
......@@ -650,6 +681,39 @@ class BuiltinTest(unittest.TestCase):
self.assertEqual(int('0123', 0), 83)
def test_intconversion(self):
# Test __int__()
class Foo0:
def __int__(self):
return 42
class Foo1(object):
def __int__(self):
return 42
class Foo2(int):
def __int__(self):
return 42
class Foo3(int):
def __int__(self):
return self
class Foo4(int):
def __int__(self):
return 42L
class Foo5(int):
def __int__(self):
return 42.
self.assertEqual(int(Foo0()), 42)
self.assertEqual(int(Foo1()), 42)
self.assertEqual(int(Foo2()), 42)
self.assertEqual(int(Foo3()), 0)
self.assertEqual(int(Foo4()), 42L)
self.assertRaises(TypeError, int, Foo5())
def test_intern(self):
self.assertRaises(TypeError, intern)
s = "never interned before"
......@@ -810,6 +874,39 @@ class BuiltinTest(unittest.TestCase):
self.assertRaises(ValueError, long, '53', 40)
self.assertRaises(TypeError, long, 1, 12)
def test_longconversion(self):
# Test __long__()
class Foo0:
def __long__(self):
return 42L
class Foo1(object):
def __long__(self):
return 42L
class Foo2(long):
def __long__(self):
return 42L
class Foo3(long):
def __long__(self):
return self
class Foo4(long):
def __long__(self):
return 42
class Foo5(long):
def __long__(self):
return 42.
self.assertEqual(long(Foo0()), 42L)
self.assertEqual(long(Foo1()), 42L)
self.assertEqual(long(Foo2()), 42L)
self.assertEqual(long(Foo3()), 0)
self.assertEqual(long(Foo4()), 42)
self.assertRaises(TypeError, long, Foo5())
def test_map(self):
self.assertEqual(
map(None, 'hello world'),
......
......@@ -273,6 +273,28 @@ class ComplexTest(unittest.TestCase):
self.assertAlmostEqual(complex(real=float2(17.), imag=float2(23.)), 17+23j)
self.assertRaises(TypeError, complex, float2(None))
class complex0(complex):
"""Test usage of __complex__() when inheriting from 'complex'"""
def __complex__(self):
return 42j
class complex1(complex):
"""Test usage of __complex__() with a __new__() method"""
def __new__(self, value=0j):
return complex.__new__(self, 2*value)
def __complex__(self):
return self
class complex2(complex):
"""Make sure that __complex__() calls fail if anything other than a
complex is returned"""
def __complex__(self):
return None
self.assertAlmostEqual(complex(complex0(1j)), 42j)
self.assertAlmostEqual(complex(complex1(1j)), 2j)
self.assertRaises(TypeError, complex, complex2(1j))
def test_hash(self):
for x in xrange(-30, 30):
self.assertEqual(hash(x), hash(complex(x, 0)))
......
......@@ -19,6 +19,69 @@ class StrTest(
string_tests.MixinStrUnicodeUserStringTest.test_formatting(self)
self.assertRaises(OverflowError, '%c'.__mod__, 0x1234)
def test_conversion(self):
# Make sure __str__() behaves properly
class Foo0:
def __unicode__(self):
return u"foo"
class Foo1:
def __str__(self):
return "foo"
class Foo2(object):
def __str__(self):
return "foo"
class Foo3(object):
def __str__(self):
return u"foo"
class Foo4(unicode):
def __str__(self):
return u"foo"
class Foo5(str):
def __str__(self):
return u"foo"
class Foo6(str):
def __str__(self):
return "foos"
def __unicode__(self):
return u"foou"
class Foo7(unicode):
def __str__(self):
return "foos"
def __unicode__(self):
return u"foou"
class Foo8(str):
def __new__(cls, content=""):
return str.__new__(cls, 2*content)
def __str__(self):
return self
class Foo9(str):
def __str__(self):
return "string"
def __unicode__(self):
return "not unicode"
self.assert_(str(Foo0()).startswith("<")) # this is different from __unicode__
self.assertEqual(str(Foo1()), "foo")
self.assertEqual(str(Foo2()), "foo")
self.assertEqual(str(Foo3()), "foo")
self.assertEqual(str(Foo4("bar")), "foo")
self.assertEqual(str(Foo5("bar")), "foo")
self.assertEqual(str(Foo6("bar")), "foos")
self.assertEqual(str(Foo7("bar")), "foos")
self.assertEqual(str(Foo8("foo")), "foofoo")
self.assertEqual(str(Foo9("foo")), "string")
self.assertEqual(unicode(Foo9("foo")), u"not unicode")
def test_main():
test_support.run_unittest(StrTest)
......
......@@ -389,7 +389,6 @@ class UnicodeTest(
self.assertEqual('%i%s %*.*s' % (10, 3, 5, 3, u'abc',), u'103 abc')
self.assertEqual('%c' % u'a', u'a')
def test_constructor(self):
# unicode(obj) tests (this maps to PyObject_Unicode() at C level)
......@@ -725,6 +724,69 @@ class UnicodeTest(
y = x.encode("raw-unicode-escape").decode("raw-unicode-escape")
self.assertEqual(x, y)
def test_conversion(self):
# Make sure __unicode__() works properly
class Foo0:
def __str__(self):
return "foo"
class Foo1:
def __unicode__(self):
return u"foo"
class Foo2(object):
def __unicode__(self):
return u"foo"
class Foo3(object):
def __unicode__(self):
return "foo"
class Foo4(str):
def __unicode__(self):
return "foo"
class Foo5(unicode):
def __unicode__(self):
return "foo"
class Foo6(str):
def __str__(self):
return "foos"
def __unicode__(self):
return u"foou"
class Foo7(unicode):
def __str__(self):
return "foos"
def __unicode__(self):
return u"foou"
class Foo8(unicode):
def __new__(cls, content=""):
return unicode.__new__(cls, 2*content)
def __unicode__(self):
return self
class Foo9(unicode):
def __str__(self):
return "string"
def __unicode__(self):
return "not unicode"
self.assertEqual(unicode(Foo0()), u"foo")
self.assertEqual(unicode(Foo1()), u"foo")
self.assertEqual(unicode(Foo2()), u"foo")
self.assertEqual(unicode(Foo3()), u"foo")
self.assertEqual(unicode(Foo4("bar")), u"foo")
self.assertEqual(unicode(Foo5("bar")), u"foo")
self.assertEqual(unicode(Foo6("bar")), u"foou")
self.assertEqual(unicode(Foo7("bar")), u"foou")
self.assertEqual(unicode(Foo8("foo")), u"foofoo")
self.assertEqual(str(Foo9("foo")), "string")
self.assertEqual(unicode(Foo9("foo")), u"not unicode")
def test_main():
test_support.run_unittest(UnicodeTest)
......
......@@ -12,6 +12,14 @@ What's New in Python 2.5 alpha 1?
Core and builtins
-----------------
- patch #1109424: int, long, float, complex, and unicode now check for the
proper magic slot for type conversions when subclassed. Previously the
magic slot was ignored during conversion. Semantics now match the way
subclasses of str always behaved. int/long/float, conversion of an instance
to the base class has been moved the prroper nb_* magic slot and out of
PyNumber_*().
Thanks Walter Dörwald.
- Descriptors defined in C with a PyGetSetDef structure, where the setter is
NULL, now raise an AttributeError when attempting to set or delete the
attribute. Previously a TypeError was raised, but this was inconsistent
......
......@@ -951,7 +951,19 @@ PyNumber_Int(PyObject *o)
Py_INCREF(o);
return o;
}
if (PyInt_Check(o)) {
m = o->ob_type->tp_as_number;
if (m && m->nb_int) { /* This should include subclasses of int */
PyObject *res = m->nb_int(o);
if (res && (!PyInt_Check(res) && !PyLong_Check(res))) {
PyErr_Format(PyExc_TypeError,
"__int__ returned non-int (type %.200s)",
res->ob_type->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}
if (PyInt_Check(o)) { /* A int subclass without nb_int */
PyIntObject *io = (PyIntObject*)o;
return PyInt_FromLong(io->ob_ival);
}
......@@ -964,18 +976,6 @@ PyNumber_Int(PyObject *o)
PyUnicode_GET_SIZE(o),
10);
#endif
m = o->ob_type->tp_as_number;
if (m && m->nb_int) {
PyObject *res = m->nb_int(o);
if (res && (!PyInt_Check(res) && !PyLong_Check(res))) {
PyErr_Format(PyExc_TypeError,
"__int__ returned non-int (type %.200s)",
res->ob_type->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}
if (!PyObject_AsCharBuffer(o, &buffer, &buffer_len))
return int_from_string((char*)buffer, buffer_len);
......@@ -1010,11 +1010,19 @@ PyNumber_Long(PyObject *o)
if (o == NULL)
return null_error();
if (PyLong_CheckExact(o)) {
Py_INCREF(o);
return o;
m = o->ob_type->tp_as_number;
if (m && m->nb_long) { /* This should include subclasses of long */
PyObject *res = m->nb_long(o);
if (res && (!PyInt_Check(res) && !PyLong_Check(res))) {
PyErr_Format(PyExc_TypeError,
"__long__ returned non-long (type %.200s)",
res->ob_type->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}
if (PyLong_Check(o))
if (PyLong_Check(o)) /* A long subclass without nb_long */
return _PyLong_Copy((PyLongObject *)o);
if (PyString_Check(o))
/* need to do extra error checking that PyLong_FromString()
......@@ -1030,18 +1038,6 @@ PyNumber_Long(PyObject *o)
PyUnicode_GET_SIZE(o),
10);
#endif
m = o->ob_type->tp_as_number;
if (m && m->nb_long) {
PyObject *res = m->nb_long(o);
if (res && (!PyInt_Check(res) && !PyLong_Check(res))) {
PyErr_Format(PyExc_TypeError,
"__long__ returned non-long (type %.200s)",
res->ob_type->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}
if (!PyObject_AsCharBuffer(o, &buffer, &buffer_len))
return long_from_string(buffer, buffer_len);
......@@ -1055,28 +1051,22 @@ PyNumber_Float(PyObject *o)
if (o == NULL)
return null_error();
if (PyFloat_CheckExact(o)) {
Py_INCREF(o);
return o;
m = o->ob_type->tp_as_number;
if (m && m->nb_float) { /* This should include subclasses of float */
PyObject *res = m->nb_float(o);
if (res && !PyFloat_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__float__ returned non-float (type %.200s)",
res->ob_type->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}
if (PyFloat_Check(o)) {
if (PyFloat_Check(o)) { /* A float subclass with nb_float == NULL */
PyFloatObject *po = (PyFloatObject *)o;
return PyFloat_FromDouble(po->ob_fval);
}
if (!PyString_Check(o)) {
m = o->ob_type->tp_as_number;
if (m && m->nb_float) {
PyObject *res = m->nb_float(o);
if (res && !PyFloat_Check(res)) {
PyErr_Format(PyExc_TypeError,
"__float__ returned non-float (type %.200s)",
res->ob_type->tp_name);
Py_DECREF(res);
return NULL;
}
return res;
}
}
return PyFloat_FromString(o, NULL);
}
......
......@@ -926,7 +926,10 @@ float_int(PyObject *v)
static PyObject *
float_float(PyObject *v)
{
Py_INCREF(v);
if (PyFloat_CheckExact(v))
Py_INCREF(v);
else
v = PyFloat_FromDouble(((PyFloatObject *)v)->ob_fval);
return v;
}
......
......@@ -826,7 +826,10 @@ int_coerce(PyObject **pv, PyObject **pw)
static PyObject *
int_int(PyIntObject *v)
{
Py_INCREF(v);
if (PyInt_CheckExact(v))
Py_INCREF(v);
else
v = (PyIntObject *)PyInt_FromLong(v->ob_ival);
return (PyObject *)v;
}
......
......@@ -2861,7 +2861,10 @@ long_coerce(PyObject **pv, PyObject **pw)
static PyObject *
long_long(PyObject *v)
{
Py_INCREF(v);
if (PyLong_CheckExact(v))
Py_INCREF(v);
else
v = _PyLong_Copy((PyLongObject *)v);
return v;
}
......
......@@ -373,6 +373,8 @@ PyObject *
PyObject_Unicode(PyObject *v)
{
PyObject *res;
PyObject *func;
static PyObject *unicodestr;
if (v == NULL)
res = PyString_FromString("<NULL>");
......@@ -380,35 +382,32 @@ PyObject_Unicode(PyObject *v)
Py_INCREF(v);
return v;
}
if (PyUnicode_Check(v)) {
/* For a Unicode subtype that's not a Unicode object,
return a true Unicode object with the same data. */
return PyUnicode_FromUnicode(PyUnicode_AS_UNICODE(v),
PyUnicode_GET_SIZE(v));
/* XXX As soon as we have a tp_unicode slot, we should
check this before trying the __unicode__
method. */
if (unicodestr == NULL) {
unicodestr= PyString_InternFromString("__unicode__");
if (unicodestr == NULL)
return NULL;
}
func = PyObject_GetAttr(v, unicodestr);
if (func != NULL) {
res = PyEval_CallObject(func, (PyObject *)NULL);
Py_DECREF(func);
}
if (PyString_Check(v)) {
Py_INCREF(v);
res = v;
}
else {
PyObject *func;
static PyObject *unicodestr;
/* XXX As soon as we have a tp_unicode slot, we should
check this before trying the __unicode__
method. */
if (unicodestr == NULL) {
unicodestr= PyString_InternFromString(
"__unicode__");
if (unicodestr == NULL)
return NULL;
PyErr_Clear();
if (PyUnicode_Check(v)) {
/* For a Unicode subtype that's didn't overwrite __unicode__,
return a true Unicode object with the same data. */
return PyUnicode_FromUnicode(PyUnicode_AS_UNICODE(v),
PyUnicode_GET_SIZE(v));
}
func = PyObject_GetAttr(v, unicodestr);
if (func != NULL) {
res = PyEval_CallObject(func, (PyObject *)NULL);
Py_DECREF(func);
if (PyString_CheckExact(v)) {
Py_INCREF(v);
res = v;
}
else {
PyErr_Clear();
if (v->ob_type->tp_str != NULL)
res = (*v->ob_type->tp_str)(v);
else
......@@ -424,7 +423,7 @@ PyObject_Unicode(PyObject *v)
if (str)
res = str;
else
return NULL;
return NULL;
}
return res;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment