summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeroen Demeyer <J.Demeyer@UGent.be>2019-05-28 14:42:53 +0200
committerPetr Viktorin <encukou@gmail.com>2019-05-28 14:42:53 +0200
commiteb65e2443ac21739baf6d373abc7b4638b9d6927 (patch)
tree2197fa4322a60cbe077dfb8c03e0287cd3baabd9
parent0811f2d81a12a3415dc2cb2744b41520c48d4db5 (diff)
downloadcpython-git-eb65e2443ac21739baf6d373abc7b4638b9d6927.tar.gz
bpo-36922: implement PEP-590 Py_TPFLAGS_METHOD_DESCRIPTOR (GH-13338)
Co-authored-by: Mark Shannon <mark@hotpy.org>
-rw-r--r--Doc/c-api/typeobj.rst26
-rw-r--r--Include/object.h3
-rw-r--r--Lib/test/test_capi.py24
-rw-r--r--Misc/NEWS.d/next/C API/2019-05-15-10-46-55.bpo-36922.J3EFK_.rst3
-rw-r--r--Modules/_functoolsmodule.c3
-rw-r--r--Modules/_testcapimodule.c57
-rw-r--r--Objects/descrobject.c6
-rw-r--r--Objects/funcobject.c3
-rw-r--r--Objects/object.c3
-rw-r--r--Objects/typeobject.c11
10 files changed, 132 insertions, 7 deletions
diff --git a/Doc/c-api/typeobj.rst b/Doc/c-api/typeobj.rst
index e0ea9b9b5f..aa667846a0 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 6464f33be4..d5d98d3bd8 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 a062a6563f..f3d41a20ab 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 0000000000..8eee208f90
--- /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 dcc9129fc6..13f2db939b 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 51e5d80d1f..8ba927039c 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 0db8057334..6c99f9b211 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 09b94c2642..fb7abfacb2 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 270716f397..87dba9898e 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 fc809d36e1..06e045bd15 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);