Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -5776,6 +5776,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 @@ -5823,6 +5863,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 @@ -548,7 +548,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 @@ -697,7 +698,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 @@ -623,7 +623,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 @@ -4912,7 +4912,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 @@ -5127,6 +5127,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