mirror of https://github.com/python/cpython
bpo-36922: implement PEP-590 Py_TPFLAGS_METHOD_DESCRIPTOR (GH-13338)
Co-authored-by: Mark Shannon <mark@hotpy.org>
This commit is contained in:
parent
0811f2d81a
commit
eb65e2443a
|
@ -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?
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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.
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue