From eb65e2443ac21739baf6d373abc7b4638b9d6927 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 28 May 2019 14:42:53 +0200 Subject: bpo-36922: implement PEP-590 Py_TPFLAGS_METHOD_DESCRIPTOR (GH-13338) Co-authored-by: Mark Shannon --- Doc/c-api/typeobj.rst | 26 ++++++++++ Include/object.h | 3 ++ Lib/test/test_capi.py | 24 +++++++++ .../C API/2019-05-15-10-46-55.bpo-36922.J3EFK_.rst | 3 ++ Modules/_functoolsmodule.c | 3 +- Modules/_testcapimodule.c | 57 ++++++++++++++++++++++ Objects/descrobject.c | 6 ++- Objects/funcobject.c | 3 +- Objects/object.c | 3 +- Objects/typeobject.c | 11 ++++- 10 files changed, 132 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/C API/2019-05-15-10-46-55.bpo-36922.J3EFK_.rst diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst index e0ea9b9..aa66784 100644 --- a/Doc/c-api/typeobj.rst +++ b/Doc/c-api/typeobj.rst @@ -1045,6 +1045,32 @@ and :c:type:`PyType_Type` effectively act as defaults.) ??? + + .. data:: Py_TPFLAGS_METHOD_DESCRIPTOR + + This bit indicates that objects behave like unbound methods. + + If this flag is set for ``type(meth)``, then: + + - ``meth.__get__(obj, cls)(*args, **kwds)`` (with ``obj`` not None) + must be equivalent to ``meth(obj, *args, **kwds)``. + + - ``meth.__get__(None, cls)(*args, **kwds)`` + must be equivalent to ``meth(*args, **kwds)``. + + This flag enables an optimization for typical method calls like + ``obj.meth()``: it avoids creating a temporary "bound method" object for + ``obj.meth``. + + .. versionadded:: 3.8 + + **Inheritance:** + + This flag is never inherited by heap types. + For extension types, it is inherited whenever + :c:member:`~PyTypeObject.tp_descr_get` is inherited. + + .. XXX Document more flags here? diff --git a/Include/object.h b/Include/object.h index 6464f33..d5d98d3 100644 --- a/Include/object.h +++ b/Include/object.h @@ -307,6 +307,9 @@ given type object has a specified feature. #define Py_TPFLAGS_HAVE_STACKLESS_EXTENSION 0 #endif +/* Objects behave like an unbound method */ +#define Py_TPFLAGS_METHOD_DESCRIPTOR (1UL << 17) + /* Objects support type attribute cache */ #define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18) #define Py_TPFLAGS_VALID_VERSION_TAG (1UL << 19) diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py index a062a65..f3d41a2 100644 --- a/Lib/test/test_capi.py +++ b/Lib/test/test_capi.py @@ -27,6 +27,8 @@ _testcapi = support.import_module('_testcapi') # Were we compiled --with-pydebug or with #define Py_DEBUG? Py_DEBUG = hasattr(sys, 'gettotalrefcount') +Py_TPFLAGS_METHOD_DESCRIPTOR = 1 << 17 + def testfunction(self): """some doc""" @@ -456,6 +458,28 @@ class TestPendingCalls(unittest.TestCase): self.pendingcalls_wait(l, n) +class TestPEP590(unittest.TestCase): + + def test_method_descriptor_flag(self): + import functools + cached = functools.lru_cache(1)(testfunction) + + self.assertFalse(type(repr).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + self.assertTrue(type(list.append).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + self.assertTrue(type(list.__add__).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + self.assertTrue(type(testfunction).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + self.assertTrue(type(cached).__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + + self.assertTrue(_testcapi.MethodDescriptorBase.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + self.assertTrue(_testcapi.MethodDescriptorDerived.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + self.assertFalse(_testcapi.MethodDescriptorNopGet.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + + # Heap type should not inherit Py_TPFLAGS_METHOD_DESCRIPTOR + class MethodDescriptorHeap(_testcapi.MethodDescriptorBase): + pass + self.assertFalse(MethodDescriptorHeap.__flags__ & Py_TPFLAGS_METHOD_DESCRIPTOR) + + class SubinterpreterTest(unittest.TestCase): def test_subinterps(self): diff --git a/Misc/NEWS.d/next/C API/2019-05-15-10-46-55.bpo-36922.J3EFK_.rst b/Misc/NEWS.d/next/C API/2019-05-15-10-46-55.bpo-36922.J3EFK_.rst new file mode 100644 index 0000000..8eee208 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2019-05-15-10-46-55.bpo-36922.J3EFK_.rst @@ -0,0 +1,3 @@ +Add new type flag ``Py_TPFLAGS_METHOD_DESCRIPTOR`` for objects behaving like +unbound methods. These are objects supporting the optimization given by the +``LOAD_METHOD``/``CALL_METHOD`` opcodes. See PEP 590. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index dcc9129..13f2db9 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -1333,7 +1333,8 @@ static PyTypeObject lru_cache_type = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HAVE_GC, + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | + Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ lru_cache_doc, /* tp_doc */ (traverseproc)lru_cache_tp_traverse,/* tp_traverse */ diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c index 51e5d80..8ba9270 100644 --- a/Modules/_testcapimodule.c +++ b/Modules/_testcapimodule.c @@ -5787,6 +5787,46 @@ static PyTypeObject Generic_Type = { }; +/* Test PEP 590 */ + +static PyObject * +func_descr_get(PyObject *func, PyObject *obj, PyObject *type) +{ + if (obj == Py_None || obj == NULL) { + Py_INCREF(func); + return func; + } + return PyMethod_New(func, obj); +} + +static PyObject * +nop_descr_get(PyObject *func, PyObject *obj, PyObject *type) +{ + Py_INCREF(func); + return func; +} + +static PyTypeObject MethodDescriptorBase_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "MethodDescriptorBase", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_METHOD_DESCRIPTOR, + .tp_descr_get = func_descr_get, +}; + +static PyTypeObject MethodDescriptorDerived_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "MethodDescriptorDerived", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, +}; + +static PyTypeObject MethodDescriptorNopGet_Type = { + PyVarObject_HEAD_INIT(NULL, 0) + "MethodDescriptorNopGet", + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_descr_get = nop_descr_get, +}; + + static struct PyModuleDef _testcapimodule = { PyModuleDef_HEAD_INIT, "_testcapi", @@ -5834,6 +5874,23 @@ PyInit__testcapi(void) Py_INCREF(&MyList_Type); PyModule_AddObject(m, "MyList", (PyObject *)&MyList_Type); + if (PyType_Ready(&MethodDescriptorBase_Type) < 0) + return NULL; + Py_INCREF(&MethodDescriptorBase_Type); + PyModule_AddObject(m, "MethodDescriptorBase", (PyObject *)&MethodDescriptorBase_Type); + + MethodDescriptorDerived_Type.tp_base = &MethodDescriptorBase_Type; + if (PyType_Ready(&MethodDescriptorDerived_Type) < 0) + return NULL; + Py_INCREF(&MethodDescriptorDerived_Type); + PyModule_AddObject(m, "MethodDescriptorDerived", (PyObject *)&MethodDescriptorDerived_Type); + + MethodDescriptorNopGet_Type.tp_base = &MethodDescriptorBase_Type; + if (PyType_Ready(&MethodDescriptorNopGet_Type) < 0) + return NULL; + Py_INCREF(&MethodDescriptorNopGet_Type); + PyModule_AddObject(m, "MethodDescriptorNopGet", (PyObject *)&MethodDescriptorNopGet_Type); + if (PyType_Ready(&GenericAlias_Type) < 0) return NULL; Py_INCREF(&GenericAlias_Type); diff --git a/Objects/descrobject.c b/Objects/descrobject.c index 0db8057..6c99f9b 100644 --- a/Objects/descrobject.c +++ b/Objects/descrobject.c @@ -556,7 +556,8 @@ PyTypeObject PyMethodDescr_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ 0, /* tp_doc */ descr_traverse, /* tp_traverse */ 0, /* tp_clear */ @@ -705,7 +706,8 @@ PyTypeObject PyWrapperDescr_Type = { PyObject_GenericGetAttr, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ 0, /* tp_doc */ descr_traverse, /* tp_traverse */ 0, /* tp_clear */ diff --git a/Objects/funcobject.c b/Objects/funcobject.c index 09b94c2..fb7abfa 100644 --- a/Objects/funcobject.c +++ b/Objects/funcobject.c @@ -663,7 +663,8 @@ PyTypeObject PyFunction_Type = { 0, /* tp_getattro */ 0, /* tp_setattro */ 0, /* tp_as_buffer */ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | + Py_TPFLAGS_METHOD_DESCRIPTOR, /* tp_flags */ func_new__doc__, /* tp_doc */ (traverseproc)func_traverse, /* tp_traverse */ (inquiry)func_clear, /* tp_clear */ diff --git a/Objects/object.c b/Objects/object.c index 270716f..87dba98 100644 --- a/Objects/object.c +++ b/Objects/object.c @@ -1155,8 +1155,7 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method) descr = _PyType_Lookup(tp, name); if (descr != NULL) { Py_INCREF(descr); - if (PyFunction_Check(descr) || - (Py_TYPE(descr) == &PyMethodDescr_Type)) { + if (PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) { meth_found = 1; } else { f = descr->ob_type->tp_descr_get; diff --git a/Objects/typeobject.c b/Objects/typeobject.c index fc809d3..06e045b 100644 --- a/Objects/typeobject.c +++ b/Objects/typeobject.c @@ -4950,7 +4950,7 @@ static void inherit_special(PyTypeObject *type, PyTypeObject *base) { - /* Copying basicsize is connected to the GC flags */ + /* Copying tp_traverse and tp_clear is connected to the GC flags */ if (!(type->tp_flags & Py_TPFLAGS_HAVE_GC) && (base->tp_flags & Py_TPFLAGS_HAVE_GC) && (!type->tp_traverse && !type->tp_clear)) { @@ -5165,6 +5165,15 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base) } { COPYSLOT(tp_descr_get); + /* Inherit Py_TPFLAGS_METHOD_DESCRIPTOR if tp_descr_get was inherited, + * but only for extension types */ + if (base->tp_descr_get && + type->tp_descr_get == base->tp_descr_get && + !(type->tp_flags & Py_TPFLAGS_HEAPTYPE) && + (base->tp_flags & Py_TPFLAGS_METHOD_DESCRIPTOR)) + { + type->tp_flags |= Py_TPFLAGS_METHOD_DESCRIPTOR; + } COPYSLOT(tp_descr_set); COPYSLOT(tp_dictoffset); COPYSLOT(tp_init); -- cgit v0.12