]> granicus.if.org Git - python/commitdiff
bpo-36922: implement PEP-590 Py_TPFLAGS_METHOD_DESCRIPTOR (GH-13338)
authorJeroen Demeyer <J.Demeyer@UGent.be>
Tue, 28 May 2019 12:42:53 +0000 (14:42 +0200)
committerPetr Viktorin <encukou@gmail.com>
Tue, 28 May 2019 12:42:53 +0000 (14:42 +0200)
Co-authored-by: Mark Shannon <mark@hotpy.org>
Doc/c-api/typeobj.rst
Include/object.h
Lib/test/test_capi.py
Misc/NEWS.d/next/C API/2019-05-15-10-46-55.bpo-36922.J3EFK_.rst [new file with mode: 0644]
Modules/_functoolsmodule.c
Modules/_testcapimodule.c
Objects/descrobject.c
Objects/funcobject.c
Objects/object.c
Objects/typeobject.c

index e0ea9b9b5f966375392e260f6434af9691aca2ef..aa667846a0da26b7e206a276425d3258b6dbca4c 100644 (file)
@@ -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?
 
 
index 6464f33be491905a02e92767d8e0b6535b9094e0..d5d98d3bd885be3cc9e8e22759b19ba54fae8095 100644 (file)
@@ -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)
index a062a6563fa2b69f138342affbc2e154f734bff8..f3d41a20ab0560b67970e33e174f9de44f6c887a 100644 (file)
@@ -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 (file)
index 0000000..8eee208
--- /dev/null
@@ -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.
index dcc9129fc6b1834c2e921763646c039572ffb1d4..13f2db939bb78ac08aa2adfdd686dacebb306e01 100644 (file)
@@ -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 */
index 51e5d80d1f51b36c1671ab0ac90683fc4b0311ba..8ba927039c2705f382b919645f6d811c4b361810 100644 (file)
@@ -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);
index 0db8057334fd3629291a942791fbc004a5800162..6c99f9b211b93b27d0ee136b4b8702dd85e25abf 100644 (file)
@@ -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 */
index 09b94c2642366690945f13ef30ced3632669ec4a..fb7abfacb2e40b87d432dc1ac097c3c65874d75c 100644 (file)
@@ -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 */
index 270716f397c97492e229d205cf17cafee61165b9..87dba9898e3a8a36d32dbb5d531db2973c5b6b27 100644 (file)
@@ -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;
index fc809d36e10be0c12c70ab9e5ed68814318069a5..06e045bd1597f0ab58d7fb0d61cb0a3db25195d5 100644 (file)
@@ -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);