use __qualname__ to compute bound method repr (closes #21389)
Patch from Steven Barker.
This commit is contained in:
parent
2b7ccbda90
commit
48ad7c0b01
|
@ -157,8 +157,9 @@ class TestDefaultDict(unittest.TestCase):
|
|||
def _factory(self):
|
||||
return []
|
||||
d = sub()
|
||||
self.assertTrue(repr(d).startswith(
|
||||
"defaultdict(<bound method sub._factory of defaultdict(..."))
|
||||
self.assertRegex(repr(d),
|
||||
r"defaultdict\(<bound method .*sub\._factory "
|
||||
r"of 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.
|
||||
|
|
|
@ -4423,6 +4423,61 @@ order (MRO) for bases """
|
|||
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"<bound method .*Foo\.method of <.*Foo object at .*>>")
|
||||
|
||||
|
||||
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"<bound method .*Base\.method of <.*Base object at .*>>")
|
||||
self.assertRegex(repr(derived1.method),
|
||||
r"<bound method .*Base\.method of <.*Derived1 object at .*>>")
|
||||
self.assertRegex(repr(derived2.method),
|
||||
r"<bound method .*Derived2\.method of <.*Derived2 object at .*>>")
|
||||
self.assertRegex(repr(super_d2.method),
|
||||
r"<bound method .*Base\.method of <.*Derived2 object at .*>>")
|
||||
|
||||
class Foo:
|
||||
@classmethod
|
||||
def method(cls):
|
||||
pass
|
||||
foo = Foo()
|
||||
self.assertRegex(repr(foo.method), # access via instance
|
||||
r"<bound method .*Foo\.method of <class '.*Foo'>>")
|
||||
self.assertRegex(repr(Foo.method), # access via the class
|
||||
r"<bound method .*Foo\.method of <class '.*Foo'>>")
|
||||
|
||||
|
||||
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"<bound method \? of <object object at .*>>")
|
||||
func.__name__ = "name"
|
||||
self.assertRegex(repr(method),
|
||||
r"<bound method name of <object object at .*>>")
|
||||
func.__qualname__ = "qualname"
|
||||
self.assertRegex(repr(method),
|
||||
r"<bound method qualname of <object object at .*>>")
|
||||
|
||||
|
||||
class DictProxyTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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("<bound method %V.%V of %R>",
|
||||
klassname, defname,
|
||||
result = PyUnicode_FromFormat("<bound method %V of %R>",
|
||||
funcname, defname, self);
|
||||
|
||||
Py_XDECREF(funcname);
|
||||
Py_XDECREF(klassname);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue