Skip to content

Commit

Permalink
bpo-31506: Improve the error message logic for class instantiation (G…
Browse files Browse the repository at this point in the history
…H-4740)

The error messages in `object.__new__` and `object.__init__` now aim
to point the user more directly at the name of the class being instantiated
in cases where they *haven't* been overridden (on the assumption that
the actual problem is a missing `__new__` or `__init__` definition in the
class body).

When they *have* been overridden, the errors still report themselves as
coming from object, on the assumption that the problem is with the call
up to the base class in the method implementation, rather than with the
way the constructor is being called.
  • Loading branch information
CuriousLearner authored and ncoghlan committed Dec 10, 2017
1 parent 60ed130 commit 780acc8
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 3 deletions.
49 changes: 49 additions & 0 deletions Lib/test/test_class.py
Expand Up @@ -595,5 +595,54 @@ class A:
with self.assertRaises(TypeError):
type.__setattr__(A, b'x', None)

def testConstructorErrorMessages(self):
# bpo-31506: Improves the error message logic for object_new & object_init

# Class without any method overrides
class C:
pass

with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
C(42)

with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
C.__new__(C, 42)

with self.assertRaisesRegex(TypeError, r'C\(\).__init__\(\) takes no arguments'):
C().__init__(42)

with self.assertRaisesRegex(TypeError, r'C\(\) takes no arguments'):
object.__new__(C, 42)

with self.assertRaisesRegex(TypeError, r'C\(\).__init__\(\) takes no arguments'):
object.__init__(C(), 42)

# Class with both `__init__` & `__new__` method overriden
class D:
def __new__(cls, *args, **kwargs):
super().__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

with self.assertRaisesRegex(TypeError, r'object.__new__\(\) takes no argument'):
D(42)

with self.assertRaisesRegex(TypeError, r'object.__new__\(\) takes no argument'):
D.__new__(D, 42)

with self.assertRaisesRegex(TypeError, r'object.__new__\(\) takes no argument'):
object.__new__(D, 42)

# Class that only overrides __init__
class E:
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

with self.assertRaisesRegex(TypeError, r'object.__init__\(\) takes no argument'):
E().__init__(42)

with self.assertRaisesRegex(TypeError, r'object.__init__\(\) takes no argument'):
object.__init__(E(), 42)

if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,2 @@
Improve the error message logic for object.__new__ and object.__init__.
Patch by Sanyam Khurana.
6 changes: 3 additions & 3 deletions Objects/typeobject.c
Expand Up @@ -3592,11 +3592,11 @@ object_init(PyObject *self, PyObject *args, PyObject *kwds)
PyTypeObject *type = Py_TYPE(self);
if (excess_args(args, kwds)) {
if (type->tp_init != object_init) {
PyErr_SetString(PyExc_TypeError, "object() takes no arguments");
PyErr_SetString(PyExc_TypeError, "object.__init__() takes no arguments");
return -1;
}
if (type->tp_new == object_new) {
PyErr_Format(PyExc_TypeError, "%.200s() takes no arguments",
PyErr_Format(PyExc_TypeError, "%.200s().__init__() takes no arguments",
type->tp_name);
return -1;
}
Expand All @@ -3609,7 +3609,7 @@ object_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
if (excess_args(args, kwds)) {
if (type->tp_new != object_new) {
PyErr_SetString(PyExc_TypeError, "object() takes no arguments");
PyErr_SetString(PyExc_TypeError, "object.__new__() takes no arguments");
return NULL;
}
if (type->tp_init == object_init) {
Expand Down

0 comments on commit 780acc8

Please sign in to comment.