Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions Lib/test/test_descr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1614,10 +1614,14 @@ class SubSpam(spam.spamlist): pass

with self.assertRaises(TypeError) as cm:
spam_cm(list)
self.assertEqual(
str(cm.exception),
expected_errmsg = (
"descriptor 'classmeth' requires a subtype of 'xxsubtype.spamlist' "
"but received 'list'")
self.assertEqual(str(cm.exception), expected_errmsg)

with self.assertRaises(TypeError) as cm:
spam_cm.__get__(None, list)
self.assertEqual(str(cm.exception), expected_errmsg)

def test_staticmethods(self):
# Testing static methods...
Expand Down Expand Up @@ -1952,6 +1956,29 @@ class E(object):
self.assertEqual(E().foo.__func__, C.foo) # i.e., unbound
self.assertTrue(repr(C.foo.__get__(C(1))).startswith("<bound method "))

@support.impl_detail("testing error message from implementation")
def test_methods_in_c(self):
# This test checks error messages in builtin method descriptor.
# It is allowed that other Python implementations use
# different error messages.
set_add = set.add

expected_errmsg = "descriptor 'add' of 'set' object needs an argument"

with self.assertRaises(TypeError) as cm:
set_add()
self.assertEqual(cm.exception.args[0], expected_errmsg)

expected_errmsg = "descriptor 'add' for 'set' objects doesn't apply to a 'int' object"

with self.assertRaises(TypeError) as cm:
set_add(0)
self.assertEqual(cm.exception.args[0], expected_errmsg)

with self.assertRaises(TypeError) as cm:
set_add.__get__(0)
self.assertEqual(cm.exception.args[0], expected_errmsg)

def test_special_method_lookup(self):
# The lookup of special methods bypasses __getattr__ and
# __getattribute__, but they still can be descriptors.
Expand Down
26 changes: 12 additions & 14 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ descr_check(PyDescrObject *descr, PyObject *obj, PyObject **pres)
}
if (!PyObject_TypeCheck(obj, descr->d_type)) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for '%s' objects "

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why has this been changed?

There was a difference with classmethod descriptors, but classmethod descriptors were special, they are applied to types, not instances.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message is for method, not classmethod. But I changed classmethod error message too.
I feel same error message makes sense for classmethod too.
Discuss about actual error message on bpo.

"doesn't apply to '%s' object",
"descriptor '%V' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
descr_name((PyDescrObject *)descr), "?",
descr->d_type->tp_name,
obj->ob_type->tp_name);
Expand All @@ -99,7 +99,7 @@ classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)
else {
/* Wot - no type?! */
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for type '%s' "
"descriptor '%V' for type '%.100s' "
"needs either an object or a type",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name);
Expand All @@ -108,17 +108,17 @@ classmethod_get(PyMethodDescrObject *descr, PyObject *obj, PyObject *type)
}
if (!PyType_Check(type)) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for type '%s' "
"needs a type, not a '%s' as arg 2",
"descriptor '%V' for type '%.100s' "
"needs a type, not a '%.100s' as arg 2",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
type->ob_type->tp_name);
return NULL;
}
if (!PyType_IsSubtype((PyTypeObject *)type, PyDescr_TYPE(descr))) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for type '%s' "
"doesn't apply to type '%s'",
"descriptor '%V' requires a subtype of '%.100s' "
"but received '%.100s'",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
((PyTypeObject *)type)->tp_name);
Expand Down Expand Up @@ -181,7 +181,7 @@ descr_setcheck(PyDescrObject *descr, PyObject *obj, PyObject *value,
if (!PyObject_TypeCheck(obj, descr->d_type)) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' for '%.100s' objects "
"doesn't apply to '%.100s' object",
"doesn't apply to a '%.100s' object",
descr_name(descr), "?",
descr->d_type->tp_name,
obj->ob_type->tp_name);
Expand Down Expand Up @@ -239,9 +239,8 @@ methoddescr_call(PyMethodDescrObject *descr, PyObject *args, PyObject *kwargs)
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
(PyObject *)PyDescr_TYPE(descr))) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' "
"requires a '%.100s' object "
"but received a '%.100s'",
"descriptor '%V' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
self->ob_type->tp_name);
Expand Down Expand Up @@ -278,9 +277,8 @@ _PyMethodDescr_FastCallKeywords(PyObject *descrobj,
if (!_PyObject_RealIsSubclass((PyObject *)Py_TYPE(self),
(PyObject *)PyDescr_TYPE(descr))) {
PyErr_Format(PyExc_TypeError,
"descriptor '%V' "
"requires a '%.100s' object "
"but received a '%.100s'",
"descriptor '%V' for '%.100s' objects "
"doesn't apply to a '%.100s' object",
descr_name((PyDescrObject *)descr), "?",
PyDescr_TYPE(descr)->tp_name,
self->ob_type->tp_name);
Expand Down