From 48ad7c0b01de1be182c3894e1691861ccffb82ea Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Wed, 20 Aug 2014 18:41:57 -0500 Subject: [PATCH] use __qualname__ to compute bound method repr (closes #21389) Patch from Steven Barker. --- Lib/test/test_defaultdict.py | 5 ++-- Lib/test/test_descr.py | 55 ++++++++++++++++++++++++++++++++++++ Misc/NEWS | 3 ++ Objects/classobject.c | 45 +++++++++-------------------- 4 files changed, 75 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_defaultdict.py b/Lib/test/test_defaultdict.py index 532d535981b3..48d1cb537bc1 100644 --- a/Lib/test/test_defaultdict.py +++ b/Lib/test/test_defaultdict.py @@ -157,8 +157,9 @@ def __init__(self): def _factory(self): return [] d = sub() - self.assertTrue(repr(d).startswith( - "defaultdict(, \{\}\)") # NOTE: printing a subclass of a builtin type does not call its # tp_print slot. So this part is essentially the same test as above. diff --git a/Lib/test/test_descr.py b/Lib/test/test_descr.py index 634ba7e5dfeb..39782a41f79e 100644 --- a/Lib/test/test_descr.py +++ b/Lib/test/test_descr.py @@ -4423,6 +4423,61 @@ class Sub(Base): self.assertIn("__dict__", Base.__dict__) self.assertNotIn("__dict__", Sub.__dict__) + def test_bound_method_repr(self): + class Foo: + def method(self): + pass + self.assertRegex(repr(Foo().method), + r">") + + + class Base: + def method(self): + pass + class Derived1(Base): + pass + class Derived2(Base): + def method(self): + pass + base = Base() + derived1 = Derived1() + derived2 = Derived2() + super_d2 = super(Derived2, derived2) + self.assertRegex(repr(base.method), + r">") + self.assertRegex(repr(derived1.method), + r">") + self.assertRegex(repr(derived2.method), + r">") + self.assertRegex(repr(super_d2.method), + r">") + + class Foo: + @classmethod + def method(cls): + pass + foo = Foo() + self.assertRegex(repr(foo.method), # access via instance + r">") + self.assertRegex(repr(Foo.method), # access via the class + r">") + + + class MyCallable: + def __call__(self, arg): + pass + func = MyCallable() # func has no __name__ or __qualname__ attributes + instance = object() + method = types.MethodType(func, instance) + self.assertRegex(repr(method), + r">") + func.__name__ = "name" + self.assertRegex(repr(method), + r">") + func.__qualname__ = "qualname" + self.assertRegex(repr(method), + r">") + class DictProxyTests(unittest.TestCase): def setUp(self): diff --git a/Misc/NEWS b/Misc/NEWS index ed7865ef8b6c..e6e09edea6d9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -10,6 +10,9 @@ Release date: TBA Core and Builtins ----------------- +- Issue #21389: Displaying the __qualname__ of the underlying function in the + repr of a bound method. + - Issue #22206: Using pthread, PyThread_create_key() now sets errno to ENOMEM and returns -1 (error) on integer overflow. diff --git a/Objects/classobject.c b/Objects/classobject.c index 0c0bd47fbb76..07cb6395b414 100644 --- a/Objects/classobject.c +++ b/Objects/classobject.c @@ -15,6 +15,7 @@ static int numfree = 0; #endif _Py_IDENTIFIER(__name__); +_Py_IDENTIFIER(__qualname__); PyObject * PyMethod_Function(PyObject *im) @@ -243,51 +244,33 @@ method_repr(PyMethodObject *a) { PyObject *self = a->im_self; PyObject *func = a->im_func; - PyObject *klass; - PyObject *funcname = NULL ,*klassname = NULL, *result = NULL; - char *defname = "?"; + PyObject *funcname = NULL, *result = NULL; + const char *defname = "?"; - if (self == NULL) { - PyErr_BadInternalCall(); - return NULL; - } - klass = (PyObject*)Py_TYPE(self); - - funcname = _PyObject_GetAttrId(func, &PyId___name__); + funcname = _PyObject_GetAttrId(func, &PyId___qualname__); if (funcname == NULL) { if (!PyErr_ExceptionMatches(PyExc_AttributeError)) return NULL; PyErr_Clear(); + + funcname = _PyObject_GetAttrId(func, &PyId___name__); + if (funcname == NULL) { + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + return NULL; + PyErr_Clear(); + } } - else if (!PyUnicode_Check(funcname)) { + + if (funcname != NULL && !PyUnicode_Check(funcname)) { Py_DECREF(funcname); funcname = NULL; } - if (klass == NULL) - klassname = NULL; - else { - klassname = _PyObject_GetAttrId(klass, &PyId___name__); - if (klassname == NULL) { - if (!PyErr_ExceptionMatches(PyExc_AttributeError)) { - Py_XDECREF(funcname); - return NULL; - } - PyErr_Clear(); - } - else if (!PyUnicode_Check(klassname)) { - Py_DECREF(klassname); - klassname = NULL; - } - } - /* XXX Shouldn't use repr()/%R here! */ - result = PyUnicode_FromFormat("", - klassname, defname, + result = PyUnicode_FromFormat("", funcname, defname, self); Py_XDECREF(funcname); - Py_XDECREF(klassname); return result; }