Skip to content

Commit

Permalink
bpo-36922: implement PEP-590 Py_TPFLAGS_METHOD_DESCRIPTOR (GH-13338)
Browse files Browse the repository at this point in the history
Co-authored-by: Mark Shannon <[email protected]>
  • Loading branch information
2 people authored and encukou committed May 28, 2019
1 parent 0811f2d commit eb65e24
Show file tree
Hide file tree
Showing 10 changed files with 132 additions and 7 deletions.
26 changes: 26 additions & 0 deletions Doc/c-api/typeobj.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down
3 changes: 3 additions & 0 deletions Include/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
24 changes: 24 additions & 0 deletions Lib/test/test_capi.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
# 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"""
Expand Down Expand Up @@ -456,6 +458,28 @@ def test_pendingcalls_non_threaded(self):
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):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
3 changes: 2 additions & 1 deletion Modules/_functoolsmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
57 changes: 57 additions & 0 deletions Modules/_testcapimodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 4 additions & 2 deletions Objects/descrobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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 */
Expand Down
3 changes: 2 additions & 1 deletion Objects/funcobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
3 changes: 1 addition & 2 deletions Objects/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 10 additions & 1 deletion Objects/typeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit eb65e24

Please sign in to comment.