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?
|
.. XXX Document more flags here?
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -307,6 +307,9 @@ given type object has a specified feature.
|
||||||
#define Py_TPFLAGS_HAVE_STACKLESS_EXTENSION 0
|
#define Py_TPFLAGS_HAVE_STACKLESS_EXTENSION 0
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
/* Objects behave like an unbound method */
|
||||||
|
#define Py_TPFLAGS_METHOD_DESCRIPTOR (1UL << 17)
|
||||||
|
|
||||||
/* Objects support type attribute cache */
|
/* Objects support type attribute cache */
|
||||||
#define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18)
|
#define Py_TPFLAGS_HAVE_VERSION_TAG (1UL << 18)
|
||||||
#define Py_TPFLAGS_VALID_VERSION_TAG (1UL << 19)
|
#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?
|
# Were we compiled --with-pydebug or with #define Py_DEBUG?
|
||||||
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
|
Py_DEBUG = hasattr(sys, 'gettotalrefcount')
|
||||||
|
|
||||||
|
Py_TPFLAGS_METHOD_DESCRIPTOR = 1 << 17
|
||||||
|
|
||||||
|
|
||||||
def testfunction(self):
|
def testfunction(self):
|
||||||
"""some doc"""
|
"""some doc"""
|
||||||
|
@ -456,6 +458,28 @@ class TestPendingCalls(unittest.TestCase):
|
||||||
self.pendingcalls_wait(l, n)
|
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):
|
class SubinterpreterTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_subinterps(self):
|
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_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
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 */
|
/* tp_flags */
|
||||||
lru_cache_doc, /* tp_doc */
|
lru_cache_doc, /* tp_doc */
|
||||||
(traverseproc)lru_cache_tp_traverse,/* tp_traverse */
|
(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 = {
|
static struct PyModuleDef _testcapimodule = {
|
||||||
PyModuleDef_HEAD_INIT,
|
PyModuleDef_HEAD_INIT,
|
||||||
"_testcapi",
|
"_testcapi",
|
||||||
|
@ -5834,6 +5874,23 @@ PyInit__testcapi(void)
|
||||||
Py_INCREF(&MyList_Type);
|
Py_INCREF(&MyList_Type);
|
||||||
PyModule_AddObject(m, "MyList", (PyObject *)&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)
|
if (PyType_Ready(&GenericAlias_Type) < 0)
|
||||||
return NULL;
|
return NULL;
|
||||||
Py_INCREF(&GenericAlias_Type);
|
Py_INCREF(&GenericAlias_Type);
|
||||||
|
|
|
@ -556,7 +556,8 @@ PyTypeObject PyMethodDescr_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
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 */
|
0, /* tp_doc */
|
||||||
descr_traverse, /* tp_traverse */
|
descr_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
|
@ -705,7 +706,8 @@ PyTypeObject PyWrapperDescr_Type = {
|
||||||
PyObject_GenericGetAttr, /* tp_getattro */
|
PyObject_GenericGetAttr, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
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 */
|
0, /* tp_doc */
|
||||||
descr_traverse, /* tp_traverse */
|
descr_traverse, /* tp_traverse */
|
||||||
0, /* tp_clear */
|
0, /* tp_clear */
|
||||||
|
|
|
@ -663,7 +663,8 @@ PyTypeObject PyFunction_Type = {
|
||||||
0, /* tp_getattro */
|
0, /* tp_getattro */
|
||||||
0, /* tp_setattro */
|
0, /* tp_setattro */
|
||||||
0, /* tp_as_buffer */
|
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 */
|
func_new__doc__, /* tp_doc */
|
||||||
(traverseproc)func_traverse, /* tp_traverse */
|
(traverseproc)func_traverse, /* tp_traverse */
|
||||||
(inquiry)func_clear, /* tp_clear */
|
(inquiry)func_clear, /* tp_clear */
|
||||||
|
|
|
@ -1155,8 +1155,7 @@ _PyObject_GetMethod(PyObject *obj, PyObject *name, PyObject **method)
|
||||||
descr = _PyType_Lookup(tp, name);
|
descr = _PyType_Lookup(tp, name);
|
||||||
if (descr != NULL) {
|
if (descr != NULL) {
|
||||||
Py_INCREF(descr);
|
Py_INCREF(descr);
|
||||||
if (PyFunction_Check(descr) ||
|
if (PyType_HasFeature(Py_TYPE(descr), Py_TPFLAGS_METHOD_DESCRIPTOR)) {
|
||||||
(Py_TYPE(descr) == &PyMethodDescr_Type)) {
|
|
||||||
meth_found = 1;
|
meth_found = 1;
|
||||||
} else {
|
} else {
|
||||||
f = descr->ob_type->tp_descr_get;
|
f = descr->ob_type->tp_descr_get;
|
||||||
|
|
|
@ -4950,7 +4950,7 @@ static void
|
||||||
inherit_special(PyTypeObject *type, PyTypeObject *base)
|
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) &&
|
if (!(type->tp_flags & Py_TPFLAGS_HAVE_GC) &&
|
||||||
(base->tp_flags & Py_TPFLAGS_HAVE_GC) &&
|
(base->tp_flags & Py_TPFLAGS_HAVE_GC) &&
|
||||||
(!type->tp_traverse && !type->tp_clear)) {
|
(!type->tp_traverse && !type->tp_clear)) {
|
||||||
|
@ -5165,6 +5165,15 @@ inherit_slots(PyTypeObject *type, PyTypeObject *base)
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
COPYSLOT(tp_descr_get);
|
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_descr_set);
|
||||||
COPYSLOT(tp_dictoffset);
|
COPYSLOT(tp_dictoffset);
|
||||||
COPYSLOT(tp_init);
|
COPYSLOT(tp_init);
|
||||||
|
|
Loading…
Reference in New Issue