Skip to content

Commit 9d57481

Browse files
committed
Issue #13577: various kinds of descriptors now have a __qualname__ attribute.
Patch by sbt.
1 parent 16e6a80 commit 9d57481

File tree

4 files changed

+74
-4
lines changed

4 files changed

+74
-4
lines changed

Include/descrobject.h

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ typedef struct {
4242
PyObject_HEAD
4343
PyTypeObject *d_type;
4444
PyObject *d_name;
45+
PyObject *d_qualname;
4546
} PyDescrObject;
4647

4748
#define PyDescr_COMMON PyDescrObject d_common

Lib/test/test_descr.py

+18
Original file line numberDiff line numberDiff line change
@@ -4442,6 +4442,24 @@ class X:
44424442
self.assertIn("can't delete X.__doc__", str(cm.exception))
44434443
self.assertEqual(X.__doc__, "banana")
44444444

4445+
def test_qualname(self):
4446+
descriptors = [str.lower, complex.real, float.real, int.__add__]
4447+
types = ['method', 'member', 'getset', 'wrapper']
4448+
4449+
# make sure we have an example of each type of descriptor
4450+
for d, n in zip(descriptors, types):
4451+
self.assertEqual(type(d).__name__, n + '_descriptor')
4452+
4453+
for d in descriptors:
4454+
qualname = d.__objclass__.__qualname__ + '.' + d.__name__
4455+
self.assertEqual(d.__qualname__, qualname)
4456+
4457+
self.assertEqual(str.lower.__qualname__, 'str.lower')
4458+
self.assertEqual(complex.real.__qualname__, 'complex.real')
4459+
self.assertEqual(float.real.__qualname__, 'float.real')
4460+
self.assertEqual(int.__add__.__qualname__, 'int.__add__')
4461+
4462+
44454463
class DictProxyTests(unittest.TestCase):
44464464
def setUp(self):
44474465
class C(object):

Lib/test/test_sys.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -670,17 +670,17 @@ def inner():
670670
# complex
671671
check(complex(0,1), size(h + '2d'))
672672
# method_descriptor (descriptor object)
673-
check(str.lower, size(h + '2PP'))
673+
check(str.lower, size(h + '3PP'))
674674
# classmethod_descriptor (descriptor object)
675675
# XXX
676676
# member_descriptor (descriptor object)
677677
import datetime
678-
check(datetime.timedelta.days, size(h + '2PP'))
678+
check(datetime.timedelta.days, size(h + '3PP'))
679679
# getset_descriptor (descriptor object)
680680
import collections
681-
check(collections.defaultdict.default_factory, size(h + '2PP'))
681+
check(collections.defaultdict.default_factory, size(h + '3PP'))
682682
# wrapper_descriptor (descriptor object)
683-
check(int.__add__, size(h + '2P2P'))
683+
check(int.__add__, size(h + '3P2P'))
684684
# method-wrapper (descriptor object)
685685
check({}.__iter__, size(h + '2P'))
686686
# dict

Objects/descrobject.c

+51
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ descr_dealloc(PyDescrObject *descr)
99
_PyObject_GC_UNTRACK(descr);
1010
Py_XDECREF(descr->d_type);
1111
Py_XDECREF(descr->d_name);
12+
Py_XDECREF(descr->d_qualname);
1213
PyObject_GC_Del(descr);
1314
}
1415

@@ -321,6 +322,44 @@ method_get_doc(PyMethodDescrObject *descr, void *closure)
321322
return PyUnicode_FromString(descr->d_method->ml_doc);
322323
}
323324

325+
static PyObject *
326+
calculate_qualname(PyDescrObject *descr)
327+
{
328+
PyObject *type_qualname, *res;
329+
_Py_IDENTIFIER(__qualname__);
330+
331+
if (descr->d_name == NULL || !PyUnicode_Check(descr->d_name)) {
332+
PyErr_SetString(PyExc_TypeError,
333+
"<descriptor>.__name__ is not a unicode object");
334+
return NULL;
335+
}
336+
337+
type_qualname = _PyObject_GetAttrId((PyObject *)descr->d_type,
338+
&PyId___qualname__);
339+
if (type_qualname == NULL)
340+
return NULL;
341+
342+
if (!PyUnicode_Check(type_qualname)) {
343+
PyErr_SetString(PyExc_TypeError, "<descriptor>.__objclass__."
344+
"__qualname__ is not a unicode object");
345+
Py_XDECREF(type_qualname);
346+
return NULL;
347+
}
348+
349+
res = PyUnicode_FromFormat("%S.%S", type_qualname, descr->d_name);
350+
Py_DECREF(type_qualname);
351+
return res;
352+
}
353+
354+
static PyObject *
355+
descr_get_qualname(PyDescrObject *descr)
356+
{
357+
if (descr->d_qualname == NULL)
358+
descr->d_qualname = calculate_qualname(descr);
359+
Py_XINCREF(descr->d_qualname);
360+
return descr->d_qualname;
361+
}
362+
324363
static PyMemberDef descr_members[] = {
325364
{"__objclass__", T_OBJECT, offsetof(PyDescrObject, d_type), READONLY},
326365
{"__name__", T_OBJECT, offsetof(PyDescrObject, d_name), READONLY},
@@ -329,6 +368,7 @@ static PyMemberDef descr_members[] = {
329368

330369
static PyGetSetDef method_getset[] = {
331370
{"__doc__", (getter)method_get_doc},
371+
{"__qualname__", (getter)descr_get_qualname},
332372
{0}
333373
};
334374

@@ -344,6 +384,7 @@ member_get_doc(PyMemberDescrObject *descr, void *closure)
344384

345385
static PyGetSetDef member_getset[] = {
346386
{"__doc__", (getter)member_get_doc},
387+
{"__qualname__", (getter)descr_get_qualname},
347388
{0}
348389
};
349390

@@ -359,6 +400,7 @@ getset_get_doc(PyGetSetDescrObject *descr, void *closure)
359400

360401
static PyGetSetDef getset_getset[] = {
361402
{"__doc__", (getter)getset_get_doc},
403+
{"__qualname__", (getter)descr_get_qualname},
362404
{0}
363405
};
364406

@@ -374,6 +416,7 @@ wrapperdescr_get_doc(PyWrapperDescrObject *descr, void *closure)
374416

375417
static PyGetSetDef wrapperdescr_getset[] = {
376418
{"__doc__", (getter)wrapperdescr_get_doc},
419+
{"__qualname__", (getter)descr_get_qualname},
377420
{0}
378421
};
379422

@@ -585,6 +628,7 @@ descr_new(PyTypeObject *descrtype, PyTypeObject *type, const char *name)
585628
Py_DECREF(descr);
586629
descr = NULL;
587630
}
631+
descr->d_qualname = NULL;
588632
}
589633
return descr;
590634
}
@@ -987,9 +1031,16 @@ wrapper_doc(wrapperobject *wp)
9871031
}
9881032
}
9891033

1034+
static PyObject *
1035+
wrapper_qualname(wrapperobject *wp)
1036+
{
1037+
return descr_get_qualname((PyDescrObject *)wp->descr);
1038+
}
1039+
9901040
static PyGetSetDef wrapper_getsets[] = {
9911041
{"__objclass__", (getter)wrapper_objclass},
9921042
{"__name__", (getter)wrapper_name},
1043+
{"__qualname__", (getter)wrapper_qualname},
9931044
{"__doc__", (getter)wrapper_doc},
9941045
{0}
9951046
};

0 commit comments

Comments
 (0)