Skip to content

Commit eb65e24

Browse files
jdemeyermarkshannon
authored andcommitted
bpo-36922: implement PEP-590 Py_TPFLAGS_METHOD_DESCRIPTOR (GH-13338)
Co-authored-by: Mark Shannon <mark@hotpy.org>
1 parent 0811f2d commit eb65e24

File tree

10 files changed

+132
-7
lines changed

10 files changed

+132
-7
lines changed

Doc/c-api/typeobj.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1045,6 +1045,32 @@ and :c:type:`PyType_Type` effectively act as defaults.)
10451045

10461046
???
10471047

1048+
1049+
.. data:: Py_TPFLAGS_METHOD_DESCRIPTOR
1050+
1051+
This bit indicates that objects behave like unbound methods.
1052+
1053+
If this flag is set for ``type(meth)``, then:
1054+
1055+
- ``meth.__get__(obj, cls)(*args, **kwds)`` (with ``obj`` not None)
1056+
must be equivalent to ``meth(obj, *args, **kwds)``.
1057+
1058+
- ``meth.__get__(None, cls)(*args, **kwds)``
1059+
must be equivalent to ``meth(*args, **kwds)``.
1060+
1061+
This flag enables an optimization for typical method calls like
1062+
``obj.meth()``: it avoids creating a temporary "bound method" object for
1063+
``obj.meth``.
1064+
1065+
.. versionadded:: 3.8
1066+
1067+
**Inheritance:**
1068+
1069+
This flag is never inherited by heap types.
1070+
For extension types, it is inherited whenever
1071+
:c:member:`~PyTypeObject.tp_descr_get` is inherited.
1072+
1073+
10481074
.. XXX Document more flags here?
10491075
10501076

Include/object.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ given type object has a specified feature.
307307
#define Py_TPFLAGS_HAVE_STACKLESS_EXTENSION 0
308308
#endif
309309

310+
/* Objects behave like an unbound method */
311+
#define Py_TPFLAGS_METHOD_DESCRIPTOR (1UL << 17)
312+
310313
/* Objects support type attribute cache */
311314
#define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18)
312315
#define Py_TPFLAGS_VALID_VERSION_TAG (1UL << 19)

Lib/test/test_capi.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
# Were we compiled --with-pydebug or with #define Py_DEBUG?
2828
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
2929

30+
Py_TPFLAGS_METHOD_DESCRIPTOR = 1 << 17
31+
3032

3133
def testfunction(self):
3234
"""some doc"""
@@ -456,6 +458,28 @@ def test_pendingcalls_non_threaded(self):
456458
self.pendingcalls_wait(l, n)
457459

458460

461+
class TestPEP590(unittest.TestCase):
462+
463+
def test_method_descriptor_flag(self):
464+
import functools
465+
cached = functools.lru_cache(1)(testfunction)
466+
467+
self.assertFalse(type(repr).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
468+
self.assertTrue(type(list.append).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
469+
self.assertTrue(type(list.__add__).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
470+
self.assertTrue(type(testfunction).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
471+
self.assertTrue(type(cached).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
472+
473+
self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
474+
self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
475+
self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
476+
477+
# Heap type should not inherit Py_TPFLAGS_METHOD_DESCRIPTOR
478+
class MethodDescriptorHeap(_testcapi.MethodDescriptorBase):
479+
pass
480+
self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR)
481+
482+
459483
class SubinterpreterTest(unittest.TestCase):
460484

461485
def test_subinterps(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add new type flag ``Py_TPFLAGS_METHOD_DESCRIPTOR`` for objects behaving like
2+
unbound methods. These are objects supporting the optimization given by the
3+
``LOAD_METHOD``/``CALL_METHOD`` opcodes. See PEP 590.

Modules/_functoolsmodule.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1333,7 +1333,8 @@ static PyTypeObject lru_cache_type = {
13331333
0, /* tp_getattro */
13341334
0, /* tp_setattro */
13351335
0, /* tp_as_buffer */
1336-
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC,
1336+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
1337+
Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_METHOD_DESCRIPTOR,
13371338
/* tp_flags */
13381339
lru_cache_doc, /* tp_doc */
13391340
(traverseproc)lru_cache_tp_traverse,/* tp_traverse */

Modules/_testcapimodule.c

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5787,6 +5787,46 @@ static PyTypeObject Generic_Type = {
57875787
};
57885788

57895789

5790+
/* Test PEP 590 */
5791+
5792+
static PyObject *
5793+
func_descr_get(PyObject *func, PyObject *obj, PyObject *type)
5794+
{
5795+
if (obj == Py_None || obj == NULL) {
5796+
Py_INCREF(func);
5797+
return func;
5798+
}
5799+
return PyMethod_New(func, obj);
5800+
}
5801+
5802+
static PyObject *
5803+
nop_descr_get(PyObject *func, PyObject *obj, PyObject *type)
5804+
{
5805+
Py_INCREF(func);
5806+
return func;
5807+
}
5808+
5809+
static PyTypeObject MethodDescriptorBase_Type = {
5810+
PyVarObject_HEAD_INIT(NULL, 0)
5811+
"MethodDescriptorBase",
5812+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_METHOD_DESCRIPTOR,
5813+
.tp_descr_get = func_descr_get,
5814+
};
5815+
5816+
static PyTypeObject MethodDescriptorDerived_Type = {
5817+
PyVarObject_HEAD_INIT(NULL, 0)
5818+
"MethodDescriptorDerived",
5819+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
5820+
};
5821+
5822+
static PyTypeObject MethodDescriptorNopGet_Type = {
5823+
PyVarObject_HEAD_INIT(NULL, 0)
5824+
"MethodDescriptorNopGet",
5825+
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
5826+
.tp_descr_get = nop_descr_get,
5827+
};
5828+
5829+
57905830
static struct PyModuleDef _testcapimodule = {
57915831
PyModuleDef_HEAD_INIT,
57925832
"_testcapi",
@@ -5834,6 +5874,23 @@ PyInit__testcapi(void)
58345874
Py_INCREF(&MyList_Type);
58355875
PyModule_AddObject(m, "MyList", (PyObject *)&MyList_Type);
58365876

5877+
if (PyType_Ready(&MethodDescriptorBase_Type) < 0)
5878+
return NULL;
5879+
Py_INCREF(&MethodDescriptorBase_Type);
5880+
PyModule_AddObject(m, "MethodDescriptorBase", (PyObject *)&MethodDescriptorBase_Type);
5881+
5882+
MethodDescriptorDerived_Type.tp_base = &MethodDescriptorBase_Type;
5883+
if (PyType_Ready(&MethodDescriptorDerived_Type) < 0)
5884+
return NULL;
5885+
Py_INCREF(&MethodDescriptorDerived_Type);
5886+
PyModule_AddObject(m, "MethodDescriptorDerived", (PyObject *)&MethodDescriptorDerived_Type);
5887+
5888+
MethodDescriptorNopGet_Type.tp_base = &MethodDescriptorBase_Type;
5889+
if (PyType_Ready(&MethodDescriptorNopGet_Type) < 0)
5890+
return NULL;
5891+
Py_INCREF(&MethodDescriptorNopGet_Type);
5892+
PyModule_AddObject(m, "MethodDescriptorNopGet", (PyObject *)&MethodDescriptorNopGet_Type);
5893+
58375894
if (PyType_Ready(&GenericAlias_Type) < 0)
58385895
return NULL;
58395896
Py_INCREF(&GenericAlias_Type);

Objects/descrobject.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,8 @@ PyTypeObject PyMethodDescr_Type = {
556556
PyObject_GenericGetAttr, /* tp_getattro */
557557
0, /* tp_setattro */
558558
0, /* tp_as_buffer */
559-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
559+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
560+
Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */
560561
0, /* tp_doc */
561562
descr_traverse, /* tp_traverse */
562563
0, /* tp_clear */
@@ -705,7 +706,8 @@ PyTypeObject PyWrapperDescr_Type = {
705706
PyObject_GenericGetAttr, /* tp_getattro */
706707
0, /* tp_setattro */
707708
0, /* tp_as_buffer */
708-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
709+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
710+
Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */
709711
0, /* tp_doc */
710712
descr_traverse, /* tp_traverse */
711713
0, /* tp_clear */

Objects/funcobject.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -663,7 +663,8 @@ PyTypeObject PyFunction_Type = {
663663
0, /* tp_getattro */
664664
0, /* tp_setattro */
665665
0, /* tp_as_buffer */
666-
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */
666+
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
667+
Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */
667668
func_new__doc__, /* tp_doc */
668669
(traverseproc)func_traverse, /* tp_traverse */
669670
(inquiry)func_clear, /* tp_clear */

Objects/object.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1155,8 +1155,7 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
11551155
descr = _PyType_Lookup(tp, name);
11561156
if (descr != NULL) {
11571157
Py_INCREF(descr);
1158-
if (PyFunction_Check(descr) ||
1159-
(Py_TYPE(descr) == &PyMethodDescr_Type)) {
1158+
if (PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
11601159
meth_found = 1;
11611160
} else {
11621161
f = descr->ob_type->tp_descr_get;

Objects/typeobject.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4950,7 +4950,7 @@ static void
49504950
inherit_special(PyTypeObject *type, PyTypeObject *base)
49514951
{
49524952

4953-
/* Copying basicsize is connected to the GC flags */
4953+
/* Copying tp_traverse and tp_clear is connected to the GC flags */
49544954
if (!(type->tp_flags & Py_TPFLAGS_HAVE_GC) &&
49554955
(base->tp_flags & Py_TPFLAGS_HAVE_GC) &&
49564956
(!type->tp_traverse && !type->tp_clear)) {
@@ -5165,6 +5165,15 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
51655165
}
51665166
{
51675167
COPYSLOT(tp_descr_get);
5168+
/* Inherit Py_TPFLAGS_METHOD_DESCRIPTOR if tp_descr_get was inherited,
5169+
* but only for extension types */
5170+
if (base->tp_descr_get &&
5171+
type->tp_descr_get == base->tp_descr_get &&
5172+
!(type->tp_flags & Py_TPFLAGS_HEAPTYPE) &&
5173+
(base->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR))
5174+
{
5175+
type->tp_flags |= Py_TPFLAGS_METHOD_DESCRIPTOR;
5176+
}
51685177
COPYSLOT(tp_descr_set);
51695178
COPYSLOT(tp_dictoffset);
51705179
COPYSLOT(tp_init);

0 commit comments

Comments
 (0)