Skip to content

Commit e1becf4

Browse files
encukouMarcel Plchvstinner
authored
bpo-38787: C API for module state access from extension methods (PEP 573) (GH-19936)
Module C state is now accessible from C-defined heap type methods (PEP 573). Patch by Marcel Plch and Petr Viktorin. Co-authored-by: Marcel Plch <mplch@redhat.com> Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 4638c64 commit e1becf4

19 files changed

+797
-51
lines changed

Doc/c-api/structures.rst

+47-3
Original file line numberDiff line numberDiff line change
@@ -147,23 +147,56 @@ Implementing functions and methods
147147
value of the function as exposed in Python. The function must return a new
148148
reference.
149149
150+
The function signature is::
151+
152+
PyObject *PyCFunction(PyObject *self,
153+
PyObject *const *args);
150154
151155
.. c:type:: PyCFunctionWithKeywords
152156
153157
Type of the functions used to implement Python callables in C
154158
with signature :const:`METH_VARARGS | METH_KEYWORDS`.
159+
The function signature is::
160+
161+
PyObject *PyCFunctionWithKeywords(PyObject *self,
162+
PyObject *const *args,
163+
PyObject *kwargs);
155164
156165
157166
.. c:type:: _PyCFunctionFast
158167
159168
Type of the functions used to implement Python callables in C
160169
with signature :const:`METH_FASTCALL`.
170+
The function signature is::
161171
172+
PyObject *_PyCFunctionFast(PyObject *self,
173+
PyObject *const *args,
174+
Py_ssize_t nargs);
162175
163176
.. c:type:: _PyCFunctionFastWithKeywords
164177
165178
Type of the functions used to implement Python callables in C
166179
with signature :const:`METH_FASTCALL | METH_KEYWORDS`.
180+
The function signature is::
181+
182+
PyObject *_PyCFunctionFastWithKeywords(PyObject *self,
183+
PyObject *const *args,
184+
Py_ssize_t nargs,
185+
PyObject *kwnames);
186+
187+
.. c:type:: PyCMethod
188+
189+
Type of the functions used to implement Python callables in C
190+
with signature :const:`METH_METHOD | METH_FASTCALL | METH_KEYWORDS`.
191+
The function signature is::
192+
193+
PyObject *PyCMethod(PyObject *self,
194+
PyTypeObject *defining_class,
195+
PyObject *const *args,
196+
Py_ssize_t nargs,
197+
PyObject *kwnames)
198+
199+
.. versionadded:: 3.9
167200
168201
169202
.. c:type:: PyMethodDef
@@ -197,9 +230,7 @@ The :attr:`ml_flags` field is a bitfield which can include the following flags.
197230
The individual flags indicate either a calling convention or a binding
198231
convention.
199232
200-
There are four basic calling conventions for positional arguments
201-
and two of them can be combined with :const:`METH_KEYWORDS` to support
202-
also keyword arguments. So there are a total of 6 calling conventions:
233+
There are these calling conventions:
203234
204235
.. data:: METH_VARARGS
205236
@@ -250,6 +281,19 @@ also keyword arguments. So there are a total of 6 calling conventions:
250281
.. versionadded:: 3.7
251282
252283
284+
.. data:: METH_METHOD | METH_FASTCALL | METH_KEYWORDS
285+
286+
Extension of :const:`METH_FASTCALL | METH_KEYWORDS` supporting the *defining
287+
class*, that is, the class that contains the method in question.
288+
The defining class might be a superclass of ``Py_TYPE(self)``.
289+
290+
The method needs to be of type :c:type:`PyCMethod`, the same as for
291+
``METH_FASTCALL | METH_KEYWORDS`` with ``defining_class`` argument added after
292+
``self``.
293+
294+
.. versionadded:: 3.9
295+
296+
253297
.. data:: METH_NOARGS
254298
255299
Methods without parameters don't need to check whether arguments are given if

Doc/c-api/type.rst

+35-1
Original file line numberDiff line numberDiff line change
@@ -109,14 +109,38 @@ Type Objects
109109
110110
.. versionadded:: 3.4
111111
112+
.. c:function:: PyObject* PyType_GetModule(PyTypeObject *type)
113+
114+
Return the module object associated with the given type when the type was
115+
created using :c:func:`PyType_FromModuleAndSpec`.
116+
117+
If no module is associated with the given type, sets :py:class:`TypeError`
118+
and returns ``NULL``.
119+
120+
.. versionadded:: 3.9
121+
122+
.. c:function:: void* PyType_GetModuleState(PyTypeObject *type)
123+
124+
Return the state of the module object associated with the given type.
125+
This is a shortcut for calling :c:func:`PyModule_GetState()` on the result
126+
of :c:func:`PyType_GetModule`.
127+
128+
If no module is associated with the given type, sets :py:class:`TypeError`
129+
and returns ``NULL``.
130+
131+
If the *type* has an associated module but its state is ``NULL``,
132+
returns ``NULL`` without setting an exception.
133+
134+
.. versionadded:: 3.9
135+
112136
113137
Creating Heap-Allocated Types
114138
.............................
115139
116140
The following functions and structs are used to create
117141
:ref:`heap types <heap-types>`.
118142
119-
.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
143+
.. c:function:: PyObject* PyType_FromModuleAndSpec(PyObject *module, PyType_Spec *spec, PyObject *bases)
120144
121145
Creates and returns a heap type object from the *spec*
122146
(:const:`Py_TPFLAGS_HEAPTYPE`).
@@ -127,8 +151,18 @@ The following functions and structs are used to create
127151
If *bases* is ``NULL``, the *Py_tp_base* slot is used instead.
128152
If that also is ``NULL``, the new type derives from :class:`object`.
129153
154+
The *module* must be a module object or ``NULL``.
155+
If not ``NULL``, the module is associated with the new type and can later be
156+
retreived with :c:func:`PyType_GetModule`.
157+
130158
This function calls :c:func:`PyType_Ready` on the new type.
131159
160+
.. versionadded:: 3.9
161+
162+
.. c:function:: PyObject* PyType_FromSpecWithBases(PyType_Spec *spec, PyObject *bases)
163+
164+
Equivalent to ``PyType_FromModuleAndSpec(NULL, spec, bases)``.
165+
132166
.. versionadded:: 3.3
133167
134168
.. c:function:: PyObject* PyType_FromSpec(PyType_Spec *spec)

Include/cpython/methodobject.h

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#ifndef Py_CPYTHON_METHODOBJECT_H
2+
# error "this header file must not be included directly"
3+
#endif
4+
5+
PyAPI_DATA(PyTypeObject) PyCMethod_Type;
6+
7+
/* Macros for direct access to these values. Type checks are *not*
8+
done, so use with care. */
9+
#define PyCFunction_GET_FUNCTION(func) \
10+
(((PyCFunctionObject *)func) -> m_ml -> ml_meth)
11+
#define PyCFunction_GET_SELF(func) \
12+
(((PyCFunctionObject *)func) -> m_ml -> ml_flags & METH_STATIC ? \
13+
NULL : ((PyCFunctionObject *)func) -> m_self)
14+
#define PyCFunction_GET_FLAGS(func) \
15+
(((PyCFunctionObject *)func) -> m_ml -> ml_flags)
16+
#define PyCFunction_GET_CLASS(func) \
17+
(((PyCFunctionObject *)func) -> m_ml -> ml_flags & METH_METHOD ? \
18+
((PyCMethodObject *)func) -> mm_class : NULL)
19+
20+
typedef struct {
21+
PyObject_HEAD
22+
PyMethodDef *m_ml; /* Description of the C function to call */
23+
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
24+
PyObject *m_module; /* The __module__ attribute, can be anything */
25+
PyObject *m_weakreflist; /* List of weak references */
26+
vectorcallfunc vectorcall;
27+
} PyCFunctionObject;
28+
29+
typedef struct {
30+
PyCFunctionObject func;
31+
PyTypeObject *mm_class; /* Class that defines this method */
32+
} PyCMethodObject;

Include/cpython/object.h

+1
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ typedef struct _heaptypeobject {
289289
PyBufferProcs as_buffer;
290290
PyObject *ht_name, *ht_slots, *ht_qualname;
291291
struct _dictkeysobject *ht_cached_keys;
292+
PyObject *ht_module;
292293
/* here are optional user slots, followed by the members. */
293294
} PyHeapTypeObject;
294295

Include/methodobject.h

+28-20
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ extern "C" {
1313

1414
PyAPI_DATA(PyTypeObject) PyCFunction_Type;
1515

16-
#define PyCFunction_Check(op) Py_IS_TYPE(op, &PyCFunction_Type)
16+
#define PyCFunction_Check(op) (Py_IS_TYPE(op, &PyCFunction_Type) || (PyType_IsSubtype(Py_TYPE(op), &PyCFunction_Type)))
1717

1818
typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
1919
typedef PyObject *(*_PyCFunctionFast) (PyObject *, PyObject *const *, Py_ssize_t);
@@ -22,21 +22,13 @@ typedef PyObject *(*PyCFunctionWithKeywords)(PyObject *, PyObject *,
2222
typedef PyObject *(*_PyCFunctionFastWithKeywords) (PyObject *,
2323
PyObject *const *, Py_ssize_t,
2424
PyObject *);
25+
typedef PyObject *(*PyCMethod)(PyObject *, PyTypeObject *, PyObject *const *,
26+
size_t, PyObject *);
27+
2528
PyAPI_FUNC(PyCFunction) PyCFunction_GetFunction(PyObject *);
2629
PyAPI_FUNC(PyObject *) PyCFunction_GetSelf(PyObject *);
2730
PyAPI_FUNC(int) PyCFunction_GetFlags(PyObject *);
2831

29-
/* Macros for direct access to these values. Type checks are *not*
30-
done, so use with care. */
31-
#ifndef Py_LIMITED_API
32-
#define PyCFunction_GET_FUNCTION(func) \
33-
(((PyCFunctionObject *)func) -> m_ml -> ml_meth)
34-
#define PyCFunction_GET_SELF(func) \
35-
(((PyCFunctionObject *)func) -> m_ml -> ml_flags & METH_STATIC ? \
36-
NULL : ((PyCFunctionObject *)func) -> m_self)
37-
#define PyCFunction_GET_FLAGS(func) \
38-
(((PyCFunctionObject *)func) -> m_ml -> ml_flags)
39-
#endif
4032
Py_DEPRECATED(3.9) PyAPI_FUNC(PyObject *) PyCFunction_Call(PyObject *, PyObject *, PyObject *);
4133

4234
struct PyMethodDef {
@@ -52,6 +44,13 @@ typedef struct PyMethodDef PyMethodDef;
5244
PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
5345
PyObject *);
5446

47+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000
48+
#define PyCFunction_NewEx(ML, SELF, MOD) PyCMethod_New((ML), (SELF), (MOD), NULL)
49+
PyAPI_FUNC(PyObject *) PyCMethod_New(PyMethodDef *, PyObject *,
50+
PyObject *, PyTypeObject *);
51+
#endif
52+
53+
5554
/* Flag passed to newmethodobject */
5655
/* #define METH_OLDARGS 0x0000 -- unsupported now */
5756
#define METH_VARARGS 0x0001
@@ -84,15 +83,24 @@ PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
8483
#define METH_STACKLESS 0x0000
8584
#endif
8685

86+
/* METH_METHOD means the function stores an
87+
* additional reference to the class that defines it;
88+
* both self and class are passed to it.
89+
* It uses PyCMethodObject instead of PyCFunctionObject.
90+
* May not be combined with METH_NOARGS, METH_O, METH_CLASS or METH_STATIC.
91+
*/
92+
93+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000
94+
#define METH_METHOD 0x0200
95+
#endif
96+
97+
8798
#ifndef Py_LIMITED_API
88-
typedef struct {
89-
PyObject_HEAD
90-
PyMethodDef *m_ml; /* Description of the C function to call */
91-
PyObject *m_self; /* Passed as 'self' arg to the C func, can be NULL */
92-
PyObject *m_module; /* The __module__ attribute, can be anything */
93-
PyObject *m_weakreflist; /* List of weak references */
94-
vectorcallfunc vectorcall;
95-
} PyCFunctionObject;
99+
100+
#define Py_CPYTHON_METHODOBJECT_H
101+
#include "cpython/methodobject.h"
102+
#undef Py_CPYTHON_METHODOBJECT_H
103+
96104
#endif
97105

98106
#ifdef __cplusplus

Include/object.h

+5
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,11 @@ PyAPI_FUNC(PyObject*) PyType_FromSpecWithBases(PyType_Spec*, PyObject*);
213213
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03040000
214214
PyAPI_FUNC(void*) PyType_GetSlot(PyTypeObject*, int);
215215
#endif
216+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 >= 0x03090000
217+
PyAPI_FUNC(PyObject*) PyType_FromModuleAndSpec(PyObject *, PyType_Spec *, PyObject *);
218+
PyAPI_FUNC(PyObject *) PyType_GetModule(struct _typeobject *);
219+
PyAPI_FUNC(void *) PyType_GetModuleState(struct _typeobject *);
220+
#endif
216221

217222
/* Generic type check */
218223
PyAPI_FUNC(int) PyType_IsSubtype(PyTypeObject *, PyTypeObject *);

Lib/test/test_capi.py

+73
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import time
1414
import unittest
1515
import weakref
16+
import importlib.machinery
17+
import importlib.util
1618
from test import support
1719
from test.support import MISSING_C_DOCSTRINGS
1820
from test.support.script_helper import assert_python_failure, assert_python_ok
@@ -774,5 +776,76 @@ class PyMemDefaultTests(PyMemDebugTests):
774776
PYTHONMALLOC = ''
775777

776778

779+
class Test_ModuleStateAccess(unittest.TestCase):
780+
"""Test access to module start (PEP 573)"""
781+
782+
# The C part of the tests lives in _testmultiphase, in a module called
783+
# _testmultiphase_meth_state_access.
784+
# This module has multi-phase initialization, unlike _testcapi.
785+
786+
def setUp(self):
787+
fullname = '_testmultiphase_meth_state_access' # XXX
788+
origin = importlib.util.find_spec('_testmultiphase').origin
789+
loader = importlib.machinery.ExtensionFileLoader(fullname, origin)
790+
spec = importlib.util.spec_from_loader(fullname, loader)
791+
module = importlib.util.module_from_spec(spec)
792+
loader.exec_module(module)
793+
self.module = module
794+
795+
def test_subclass_get_module(self):
796+
"""PyType_GetModule for defining_class"""
797+
class StateAccessType_Subclass(self.module.StateAccessType):
798+
pass
799+
800+
instance = StateAccessType_Subclass()
801+
self.assertIs(instance.get_defining_module(), self.module)
802+
803+
def test_subclass_get_module_with_super(self):
804+
class StateAccessType_Subclass(self.module.StateAccessType):
805+
def get_defining_module(self):
806+
return super().get_defining_module()
807+
808+
instance = StateAccessType_Subclass()
809+
self.assertIs(instance.get_defining_module(), self.module)
810+
811+
def test_state_access(self):
812+
"""Checks methods defined with and without argument clinic
813+
814+
This tests a no-arg method (get_count) and a method with
815+
both a positional and keyword argument.
816+
"""
817+
818+
a = self.module.StateAccessType()
819+
b = self.module.StateAccessType()
820+
821+
methods = {
822+
'clinic': a.increment_count_clinic,
823+
'noclinic': a.increment_count_noclinic,
824+
}
825+
826+
for name, increment_count in methods.items():
827+
with self.subTest(name):
828+
self.assertEqual(a.get_count(), b.get_count())
829+
self.assertEqual(a.get_count(), 0)
830+
831+
increment_count()
832+
self.assertEqual(a.get_count(), b.get_count())
833+
self.assertEqual(a.get_count(), 1)
834+
835+
increment_count(3)
836+
self.assertEqual(a.get_count(), b.get_count())
837+
self.assertEqual(a.get_count(), 4)
838+
839+
increment_count(-2, twice=True)
840+
self.assertEqual(a.get_count(), b.get_count())
841+
self.assertEqual(a.get_count(), 0)
842+
843+
with self.assertRaises(TypeError):
844+
increment_count(thrice=3)
845+
846+
with self.assertRaises(TypeError):
847+
increment_count(1, 2, 3)
848+
849+
777850
if __name__ == "__main__":
778851
unittest.main()

Lib/test/test_sys.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1322,7 +1322,7 @@ def delx(self): del self.__x
13221322
'3P' # PyMappingMethods
13231323
'10P' # PySequenceMethods
13241324
'2P' # PyBufferProcs
1325-
'4P')
1325+
'5P')
13261326
class newstyleclass(object): pass
13271327
# Separate block for PyDictKeysObject with 8 keys and 5 entries
13281328
check(newstyleclass, s + calcsize("2nP2n0P") + 8 + 5*calcsize("n2P"))

Makefile.pre.in

+1
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,7 @@ PYTHON_HEADERS= \
11041104
$(srcdir)/Include/cpython/initconfig.h \
11051105
$(srcdir)/Include/cpython/interpreteridobject.h \
11061106
$(srcdir)/Include/cpython/listobject.h \
1107+
$(srcdir)/Include/cpython/methodobject.h \
11071108
$(srcdir)/Include/cpython/object.h \
11081109
$(srcdir)/Include/cpython/objimpl.h \
11091110
$(srcdir)/Include/cpython/pyerrors.h \
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Module C state is now accessible from C-defined heap type methods (:pep:`573`).
2+
Patch by Marcel Plch and Petr Viktorin.

0 commit comments

Comments
 (0)