[Python-checkins] gh-47146: Soft-deprecate structmember.h, expose its contents via Python.h (GH-99014)

encukou webhook-mailer at python.org
Tue Nov 22 02:25:49 EST 2022


https://github.com/python/cpython/commit/4d82f628c44490d6fbc3f6998d2473d1304d891f
commit: 4d82f628c44490d6fbc3f6998d2473d1304d891f
branch: main
author: Petr Viktorin <encukou at gmail.com>
committer: encukou <encukou at gmail.com>
date: 2022-11-22T08:25:43+01:00
summary:

gh-47146: Soft-deprecate structmember.h, expose its contents via Python.h (GH-99014)

The ``structmember.h`` header is deprecated, though it continues to be available
and there are no plans to remove it. There are no deprecation warnings. Old code
can stay unchanged (unless the extra include and non-namespaced macros bother
you greatly). Specifically, no uses in CPython are updated -- that would just be
unnecessary churn.
The ``structmember.h`` header is deprecated, though it continues to be
available and there are no plans to remove it.

Its contents are now available just by including ``Python.h``,
with a ``Py`` prefix added if it was missing:

- `PyMemberDef`, `PyMember_GetOne` and`PyMember_SetOne`
- Type macros like `Py_T_INT`, `Py_T_DOUBLE`, etc.
  (previously ``T_INT``, ``T_DOUBLE``, etc.)
- The flags `Py_READONLY` (previously ``READONLY``) and
  `Py_AUDIT_READ` (previously all uppercase)

Several items are not exposed from ``Python.h``:

- `T_OBJECT` (use `Py_T_OBJECT_EX`)
- `T_NONE` (previously undocumented, and pretty quirky)
- The macro ``WRITE_RESTRICTED`` which does nothing.
- The macros ``RESTRICTED`` and ``READ_RESTRICTED``, equivalents of
  `Py_AUDIT_READ`.
- In some configurations, ``<stddef.h>`` is not included from ``Python.h``.
  It should be included manually when using ``offsetof()``.

The deprecated header continues to provide its original
contents under the original names.
Your old code can stay unchanged, unless the extra include and non-namespaced
macros bother you greatly.

There is discussion on the issue to rename `T_PYSSIZET` to `PY_T_SSIZE` or
similar. I chose not to do that -- users will probably copy/paste that with any
spelling, and not renaming it makes migration docs simpler.


Co-Authored-By: Alexander Belopolsky <abalkin at users.noreply.github.com>
Co-Authored-By: Matthias Braun <MatzeB at users.noreply.github.com>

files:
A Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst
A Modules/_testcapi/structmember.c
M Doc/c-api/structures.rst
M Doc/data/stable_abi.dat
M Doc/extending/newtypes.rst
M Doc/extending/newtypes_tutorial.rst
M Doc/includes/custom2.c
M Doc/includes/custom3.c
M Doc/includes/custom4.c
M Doc/whatsnew/3.12.rst
M Include/descrobject.h
M Include/structmember.h
M Lib/test/test_capi/test_structmembers.py
M Misc/stable_abi.toml
M Modules/Setup.stdlib.in
M Modules/_testcapi/parts.h
M Modules/_testcapimodule.c
M PCbuild/_testcapi.vcxproj
M PCbuild/_testcapi.vcxproj.filters

diff --git a/Doc/c-api/structures.rst b/Doc/c-api/structures.rst
index 5a20f07214fd..827d624fc99e 100644
--- a/Doc/c-api/structures.rst
+++ b/Doc/c-api/structures.rst
@@ -385,86 +385,67 @@ Accessing attributes of extension types
 .. c:type:: PyMemberDef
 
    Structure which describes an attribute of a type which corresponds to a C
-   struct member.  Its fields are:
+   struct member.  Its fields are, in order:
 
-   .. c:member:: const char* PyMemberDef.name
+   .. c:member:: const char* name
 
-         Name of the member
+         Name of the member.
+         A NULL value marks the end of a ``PyMemberDef[]`` array.
 
-   .. c:member:: int PyMemberDef.type
-
-      The type of the member in the C struct.
+         The string should be static, no copy is made of it.
 
    .. c:member:: Py_ssize_t PyMemberDef.offset
 
       The offset in bytes that the member is located on the type’s object struct.
 
-   .. c:member:: int PyMemberDef.flags
-
-      Flag bits indicating if the field should be read-only or writable.
-
-   .. c:member:: const char* PyMemberDef.doc
-
-      Points to the contents of the docstring.
-
-   :c:member:`PyMemberDef.type` can be one of many ``T_`` macros corresponding to various C
-   types.  When the member is accessed in Python, it will be converted to the
-   equivalent Python type.
-
-   =============== ==================
-   Macro name      C type
-   =============== ==================
-   T_SHORT         short
-   T_INT           int
-   T_LONG          long
-   T_FLOAT         float
-   T_DOUBLE        double
-   T_STRING        const char \*
-   T_OBJECT        PyObject \*
-   T_OBJECT_EX     PyObject \*
-   T_CHAR          char
-   T_BYTE          char
-   T_UBYTE         unsigned char
-   T_UINT          unsigned int
-   T_USHORT        unsigned short
-   T_ULONG         unsigned long
-   T_BOOL          char
-   T_LONGLONG      long long
-   T_ULONGLONG     unsigned long long
-   T_PYSSIZET      Py_ssize_t
-   =============== ==================
-
-   :c:macro:`T_OBJECT` and :c:macro:`T_OBJECT_EX` differ in that
-   :c:macro:`T_OBJECT` returns ``None`` if the member is ``NULL`` and
-   :c:macro:`T_OBJECT_EX` raises an :exc:`AttributeError`.  Try to use
-   :c:macro:`T_OBJECT_EX` over :c:macro:`T_OBJECT` because :c:macro:`T_OBJECT_EX`
-   handles use of the :keyword:`del` statement on that attribute more correctly
-   than :c:macro:`T_OBJECT`.
-
-   :c:member:`PyMemberDef.flags` can be ``0`` for write and read access or :c:macro:`READONLY` for
-   read-only access.  Using :c:macro:`T_STRING` for :attr:`type` implies
-   :c:macro:`READONLY`.  :c:macro:`T_STRING` data is interpreted as UTF-8.
-   Only :c:macro:`T_OBJECT` and :c:macro:`T_OBJECT_EX`
-   members can be deleted.  (They are set to ``NULL``).
+   .. c:member:: int type
+
+      The type of the member in the C struct.
+      See :ref:`PyMemberDef-types` for the possible values.
+
+   .. c:member:: int flags
+
+      Zero or more of the :ref:`PyMemberDef-flags`, combined using bitwise OR.
+
+   .. c:member:: const char* doc
+
+      The docstring, or NULL.
+      The string should be static, no copy is made of it.
+      Typically, it is defined using :c:macro:`PyDoc_STR`.
+
+   By default (when :c:member:`flags` is ``0``), members allow
+   both read and write access.
+   Use the :c:macro:`Py_READONLY` flag for read-only access.
+   Certain types, like :c:macro:`Py_T_STRING`, imply :c:macro:`Py_READONLY`.
+   Only :c:macro:`Py_T_OBJECT_EX` (and legacy :c:macro:`T_OBJECT`) members can
+   be deleted.
 
    .. _pymemberdef-offsets:
 
-   Heap allocated types (created using :c:func:`PyType_FromSpec` or similar),
-   ``PyMemberDef`` may contain definitions for the special member
-   ``__vectorcalloffset__``, corresponding to
+   For heap-allocated types (created using :c:func:`PyType_FromSpec` or similar),
+   ``PyMemberDef`` may contain a definition for the special member
+   ``"__vectorcalloffset__"``, corresponding to
    :c:member:`~PyTypeObject.tp_vectorcall_offset` in type objects.
-   These must be defined with ``T_PYSSIZET`` and ``READONLY``, for example::
+   These must be defined with ``Py_T_PYSSIZET`` and ``Py_READONLY``, for example::
 
       static PyMemberDef spam_type_members[] = {
-          {"__vectorcalloffset__", T_PYSSIZET, offsetof(Spam_object, vectorcall), READONLY},
+          {"__vectorcalloffset__", Py_T_PYSSIZET,
+           offsetof(Spam_object, vectorcall), Py_READONLY},
           {NULL}  /* Sentinel */
       };
 
+   (You may need to ``#include <stddef.h>`` for :c:func:`!offsetof`.)
+
    The legacy offsets :c:member:`~PyTypeObject.tp_dictoffset` and
-   :c:member:`~PyTypeObject.tp_weaklistoffset` are still supported, but extensions are
-   strongly encouraged to use ``Py_TPFLAGS_MANAGED_DICT`` and
-   ``Py_TPFLAGS_MANAGED_WEAKREF`` instead.
+   :c:member:`~PyTypeObject.tp_weaklistoffset` can be defined similarly using
+   ``"__dictoffset__"`` and ``"__weaklistoffset__"`` members, but extensions
+   are strongly encouraged to use :const:`Py_TPFLAGS_MANAGED_DICT` and
+   :const:`Py_TPFLAGS_MANAGED_WEAKREF` instead.
 
+   .. versionchanged:: 3.12
+
+      ``PyMemberDef`` is always available.
+      Previously, it required including ``"structmember.h"``.
 
 .. c:function:: PyObject* PyMember_GetOne(const char *obj_addr, struct PyMemberDef *m)
 
@@ -472,6 +453,10 @@ Accessing attributes of extension types
    attribute is described by ``PyMemberDef`` *m*.  Returns ``NULL``
    on error.
 
+   .. versionchanged:: 3.12
+
+      ``PyMember_GetOne`` is always available.
+      Previously, it required including ``"structmember.h"``.
 
 .. c:function:: int PyMember_SetOne(char *obj_addr, struct PyMemberDef *m, PyObject *o)
 
@@ -479,6 +464,144 @@ Accessing attributes of extension types
    The attribute to set is described by ``PyMemberDef`` *m*.  Returns ``0``
    if successful and a negative value on failure.
 
+   .. versionchanged:: 3.12
+
+      ``PyMember_SetOne`` is always available.
+      Previously, it required including ``"structmember.h"``.
+
+.. _PyMemberDef-flags:
+
+Member flags
+^^^^^^^^^^^^
+
+The following flags can be used with :c:member:`PyMemberDef.flags`:
+
+.. c:macro:: Py_READONLY
+
+   Not writable.
+
+.. c:macro:: Py_AUDIT_READ
+
+   Emit an ``object.__getattr__`` :ref:`audit event <audit-events>`
+   before reading.
+
+.. index::
+   single: READ_RESTRICTED
+   single: WRITE_RESTRICTED
+   single: RESTRICTED
+
+.. versionchanged:: 3.10
+
+   The :const:`!RESTRICTED`, :const:`!READ_RESTRICTED` and
+   :const:`!WRITE_RESTRICTED` macros available with
+   ``#include "structmember.h"`` are deprecated.
+   :const:`!READ_RESTRICTED` and :const:`!RESTRICTED` are equivalent to
+   :const:`Py_AUDIT_READ`; :const:`!WRITE_RESTRICTED` does nothing.
+
+.. index::
+   single: READONLY
+
+.. versionchanged:: 3.12
+
+   The :const:`!READONLY` macro was renamed to :const:`Py_READONLY`.
+   The :const:`!PY_AUDIT_READ` macro was renamed with the ``Py_`` prefix.
+   The new names are now always available.
+   Previously, these required ``#include "structmember.h"``.
+   The header is still available and it provides the old names.
+
+.. _PyMemberDef-types:
+
+Member types
+^^^^^^^^^^^^
+
+:c:member:`PyMemberDef.type` can be one of the following macros corresponding
+to various C types.
+When the member is accessed in Python, it will be converted to the
+equivalent Python type.
+When it is set from Python, it will be converted back to the C type.
+If that is not possible, an exception such as :exc:`TypeError` or
+:exc:`ValueError` is raised.
+
+Unless marked (D), attributes defined this way cannot be deleted
+using e.g. :keyword:`del` or :py:func:`delattr`.
+
+================================ ============================= ======================
+Macro name                       C type                        Python type
+================================ ============================= ======================
+.. c:macro:: Py_T_BYTE           :c:expr:`char`                :py:class:`int`
+.. c:macro:: Py_T_SHORT          :c:expr:`short`               :py:class:`int`
+.. c:macro:: Py_T_INT            :c:expr:`int`                 :py:class:`int`
+.. c:macro:: Py_T_LONG           :c:expr:`long`                :py:class:`int`
+.. c:macro:: Py_T_LONGLONG       :c:expr:`long long`           :py:class:`int`
+.. c:macro:: Py_T_UBYTE          :c:expr:`unsigned char`       :py:class:`int`
+.. c:macro:: Py_T_UINT           :c:expr:`unsigned int`        :py:class:`int`
+.. c:macro:: Py_T_USHORT         :c:expr:`unsigned short`      :py:class:`int`
+.. c:macro:: Py_T_ULONG          :c:expr:`unsigned long`       :py:class:`int`
+.. c:macro:: Py_T_ULONGLONG      :c:expr:`unsigned long long`  :py:class:`int`
+.. c:macro:: Py_T_PYSSIZET       :c:expr:`Py_ssize_t`          :py:class:`int`
+.. c:macro:: Py_T_FLOAT          :c:expr:`float`               :py:class:`float`
+.. c:macro:: Py_T_DOUBLE         :c:expr:`double`              :py:class:`float`
+.. c:macro:: Py_T_BOOL           :c:expr:`char`                :py:class:`bool`
+                                 (written as 0 or 1)
+.. c:macro:: Py_T_STRING         :c:expr:`const char *` (*)    :py:class:`str` (RO)
+.. c:macro:: Py_T_STRING_INPLACE :c:expr:`const char[]` (*)    :py:class:`str` (RO)
+.. c:macro:: Py_T_CHAR           :c:expr:`char` (0-127)        :py:class:`str` (**)
+.. c:macro:: Py_T_OBJECT_EX      :c:expr:`PyObject *`          :py:class:`object` (D)
+================================ ============================= ======================
+
+   (*): Zero-terminated, UTF8-encoded C string.
+   With :c:macro:`!Py_T_STRING` the C representation is a pointer;
+   with :c:macro:`!Py_T_STRING_INLINE` the string is stored directly
+   in the structure.
+
+   (**): String of length 1. Only ASCII is accepted.
+
+   (RO): Implies :c:macro:`Py_READONLY`.
+
+   (D): Can be deleted, in which case the pointer is set to ``NULL``.
+   Reading a ``NULL`` pointer raises :py:exc:`AttributeError`.
+
+.. index::
+   single: T_BYTE
+   single: T_SHORT
+   single: T_INT
+   single: T_LONG
+   single: T_LONGLONG
+   single: T_UBYTE
+   single: T_USHORT
+   single: T_UINT
+   single: T_ULONG
+   single: T_ULONGULONG
+   single: T_PYSSIZET
+   single: T_FLOAT
+   single: T_DOUBLE
+   single: T_BOOL
+   single: T_CHAR
+   single: T_STRING
+   single: T_STRING_INPLACE
+   single: T_OBJECT_EX
+   single: structmember.h
+
+.. versionadded:: 3.12
+
+   In previous versions, the macros were only available with
+   ``#include "structmember.h"`` and were named without the ``Py_`` prefix
+   (e.g. as ``T_INT``).
+   The header is still available and contains the old names, along with
+   the following deprecated types:
+
+   .. c:macro:: T_OBJECT
+
+      Like ``Py_T_OBJECT_EX``, but ``NULL`` is converted to ``None``.
+      This results in surprising behavior in Python: deleting the attribute
+      effectively sets it to ``None``.
+
+   .. c:macro:: T_NONE
+
+      Always ``None``. Must be used with :c:macro:`Py_READONLY`.
+
+Defining Getters and Setters
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 .. c:type:: PyGetSetDef
 
diff --git a/Doc/data/stable_abi.dat b/Doc/data/stable_abi.dat
index db8fc15d93d1..53895bbced84 100644
--- a/Doc/data/stable_abi.dat
+++ b/Doc/data/stable_abi.dat
@@ -386,6 +386,8 @@ function,PyMem_Malloc,3.2,,
 function,PyMem_Realloc,3.2,,
 type,PyMemberDef,3.2,,full-abi
 var,PyMemberDescr_Type,3.2,,
+function,PyMember_GetOne,3.2,,
+function,PyMember_SetOne,3.2,,
 function,PyMemoryView_FromBuffer,3.11,,
 function,PyMemoryView_FromMemory,3.7,,
 function,PyMemoryView_FromObject,3.2,,
diff --git a/Doc/extending/newtypes.rst b/Doc/extending/newtypes.rst
index 3de849ade788..80a1387db200 100644
--- a/Doc/extending/newtypes.rst
+++ b/Doc/extending/newtypes.rst
@@ -286,36 +286,11 @@ be read-only or read-write.  The structures in the table are defined as::
 
 For each entry in the table, a :term:`descriptor` will be constructed and added to the
 type which will be able to extract a value from the instance structure.  The
-:attr:`type` field should contain one of the type codes defined in the
-:file:`structmember.h` header; the value will be used to determine how to
+:attr:`type` field should contain a type code like :c:macro:`Py_T_INT` or
+:c:macro:`Py_T_DOUBLE`; the value will be used to determine how to
 convert Python values to and from C values.  The :attr:`flags` field is used to
-store flags which control how the attribute can be accessed.
-
-The following flag constants are defined in :file:`structmember.h`; they may be
-combined using bitwise-OR.
-
-+---------------------------+----------------------------------------------+
-| Constant                  | Meaning                                      |
-+===========================+==============================================+
-| :const:`READONLY`         | Never writable.                              |
-+---------------------------+----------------------------------------------+
-| :const:`PY_AUDIT_READ`    | Emit an ``object.__getattr__``               |
-|                           | :ref:`audit events <audit-events>` before    |
-|                           | reading.                                     |
-+---------------------------+----------------------------------------------+
-
-.. versionchanged:: 3.10
-   :const:`RESTRICTED`, :const:`READ_RESTRICTED` and :const:`WRITE_RESTRICTED`
-   are deprecated. However, :const:`READ_RESTRICTED` is an alias for
-   :const:`PY_AUDIT_READ`, so fields that specify either :const:`RESTRICTED`
-   or :const:`READ_RESTRICTED` will also raise an audit event.
-
-.. index::
-   single: READONLY
-   single: READ_RESTRICTED
-   single: WRITE_RESTRICTED
-   single: RESTRICTED
-   single: PY_AUDIT_READ
+store flags which control how the attribute can be accessed: you can set it to
+:c:macro:`Py_READONLY` to prevent Python code from setting it.
 
 An interesting advantage of using the :c:member:`~PyTypeObject.tp_members` table to build
 descriptors that are used at runtime is that any attribute defined this way can
diff --git a/Doc/extending/newtypes_tutorial.rst b/Doc/extending/newtypes_tutorial.rst
index 5d4a3f06dd54..54de3fd42437 100644
--- a/Doc/extending/newtypes_tutorial.rst
+++ b/Doc/extending/newtypes_tutorial.rst
@@ -239,13 +239,6 @@ adds these capabilities:
 
 This version of the module has a number of changes.
 
-We've added an extra include::
-
-   #include <structmember.h>
-
-This include provides declarations that we use to handle attributes, as
-described a bit later.
-
 The  :class:`Custom` type now has three data attributes in its C struct,
 *first*, *last*, and *number*.  The *first* and *last* variables are Python
 strings containing first and last names.  The *number* attribute is a C integer.
@@ -436,11 +429,11 @@ We want to expose our instance variables as attributes. There are a
 number of ways to do that. The simplest way is to define member definitions::
 
    static PyMemberDef Custom_members[] = {
-       {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
+       {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
         "first name"},
-       {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
+       {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
         "last name"},
-       {"number", T_INT, offsetof(CustomObject, number), 0,
+       {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };
@@ -609,7 +602,7 @@ above.  In this case, we aren't using a closure, so we just pass ``NULL``.
 We also remove the member definitions for these attributes::
 
    static PyMemberDef Custom_members[] = {
-       {"number", T_INT, offsetof(CustomObject, number), 0,
+       {"number", Py_T_INT, offsetof(CustomObject, number), 0,
         "custom number"},
        {NULL}  /* Sentinel */
    };
diff --git a/Doc/includes/custom2.c b/Doc/includes/custom2.c
index aee9e1bb7f2d..6638b9fbc1d7 100644
--- a/Doc/includes/custom2.c
+++ b/Doc/includes/custom2.c
@@ -1,6 +1,6 @@
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
-#include "structmember.h"
+#include <stddef.h> /* for offsetof() */
 
 typedef struct {
     PyObject_HEAD
@@ -63,11 +63,11 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
 }
 
 static PyMemberDef Custom_members[] = {
-    {"first", T_OBJECT_EX, offsetof(CustomObject, first), 0,
+    {"first", Py_T_OBJECT_EX, offsetof(CustomObject, first), 0,
      "first name"},
-    {"last", T_OBJECT_EX, offsetof(CustomObject, last), 0,
+    {"last", Py_T_OBJECT_EX, offsetof(CustomObject, last), 0,
      "last name"},
-    {"number", T_INT, offsetof(CustomObject, number), 0,
+    {"number", Py_T_INT, offsetof(CustomObject, number), 0,
      "custom number"},
     {NULL}  /* Sentinel */
 };
diff --git a/Doc/includes/custom3.c b/Doc/includes/custom3.c
index 8d88bc245118..0faf2bd4be17 100644
--- a/Doc/includes/custom3.c
+++ b/Doc/includes/custom3.c
@@ -1,6 +1,6 @@
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
-#include "structmember.h"
+#include <stddef.h> /* for offsetof() */
 
 typedef struct {
     PyObject_HEAD
@@ -63,7 +63,7 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
 }
 
 static PyMemberDef Custom_members[] = {
-    {"number", T_INT, offsetof(CustomObject, number), 0,
+    {"number", Py_T_INT, offsetof(CustomObject, number), 0,
      "custom number"},
     {NULL}  /* Sentinel */
 };
diff --git a/Doc/includes/custom4.c b/Doc/includes/custom4.c
index ad240ae6a8df..b725bc0b6fae 100644
--- a/Doc/includes/custom4.c
+++ b/Doc/includes/custom4.c
@@ -1,6 +1,6 @@
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
-#include "structmember.h"
+#include <stddef.h> /* for offsetof() */
 
 typedef struct {
     PyObject_HEAD
@@ -79,7 +79,7 @@ Custom_init(CustomObject *self, PyObject *args, PyObject *kwds)
 }
 
 static PyMemberDef Custom_members[] = {
-    {"number", T_INT, offsetof(CustomObject, number), 0,
+    {"number", Py_T_INT, offsetof(CustomObject, number), 0,
      "custom number"},
     {NULL}  /* Sentinel */
 };
diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst
index f8786c15f6f4..8e9a4f04a890 100644
--- a/Doc/whatsnew/3.12.rst
+++ b/Doc/whatsnew/3.12.rst
@@ -849,6 +849,37 @@ Deprecated
 * Creating :c:data:`immutable types <Py_TPFLAGS_IMMUTABLETYPE>` with mutable
   bases is deprecated and will be disabled in Python 3.14.
 
+* The ``structmember.h`` header is deprecated, though it continues to be
+  available and there are no plans to remove it.
+
+  Its contents are now available just by including ``Python.h``,
+  with a ``Py`` prefix added if it was missing:
+
+  - :c:struct:`PyMemberDef`, :c:func:`PyMember_GetOne` and
+    :c:func:`PyMember_SetOne`
+  - Type macros like :c:macro:`Py_T_INT`, :c:macro:`Py_T_DOUBLE`, etc.
+    (previously ``T_INT``, ``T_DOUBLE``, etc.)
+  - The flags :c:macro:`Py_READONLY` (previously ``READONLY``) and
+    :c:macro:`Py_AUDIT_READ` (previously all uppercase)
+
+  Several items are not exposed from ``Python.h``:
+
+  - :c:macro:`T_OBJECT` (use :c:macro:`Py_T_OBJECT_EX`)
+  - :c:macro:`T_NONE` (previously undocumented, and pretty quirky)
+  - The macro ``WRITE_RESTRICTED`` which does nothing.
+  - The macros ``RESTRICTED`` and ``READ_RESTRICTED``, equivalents of
+    :c:macro:`Py_AUDIT_READ`.
+  - In some configurations, ``<stddef.h>`` is not included from ``Python.h``.
+    It should be included manually when using ``offsetof()``.
+
+  The deprecated header continues to provide its original
+  contents under the original names.
+  Your old code can stay unchanged, unless the extra include and non-namespaced
+  macros bother you greatly.
+
+  (Contributed in :gh:`47146` by Petr Viktorin, based on
+  earlier work by Alexander Belopolsky and Matthias Braun.)
+
 
 Removed
 -------
diff --git a/Include/descrobject.h b/Include/descrobject.h
index 77f221df0771..0a420b865dfd 100644
--- a/Include/descrobject.h
+++ b/Include/descrobject.h
@@ -32,6 +32,61 @@ PyAPI_FUNC(PyObject *) PyDescr_NewGetSet(PyTypeObject *, PyGetSetDef *);
 PyAPI_FUNC(PyObject *) PyDictProxy_New(PyObject *);
 PyAPI_FUNC(PyObject *) PyWrapper_New(PyObject *, PyObject *);
 
+
+/* An array of PyMemberDef structures defines the name, type and offset
+   of selected members of a C structure.  These can be read by
+   PyMember_GetOne() and set by PyMember_SetOne() (except if their READONLY
+   flag is set).  The array must be terminated with an entry whose name
+   pointer is NULL. */
+struct PyMemberDef {
+    const char *name;
+    int type;
+    Py_ssize_t offset;
+    int flags;
+    const char *doc;
+};
+
+// These constants used to be in structmember.h, not prefixed by Py_.
+// (structmember.h now has aliases to the new names.)
+
+/* Types */
+#define Py_T_SHORT     0
+#define Py_T_INT       1
+#define Py_T_LONG      2
+#define Py_T_FLOAT     3
+#define Py_T_DOUBLE    4
+#define Py_T_STRING    5
+#define _Py_T_OBJECT   6  // Deprecated, use Py_T_OBJECT_EX instead
+/* the ordering here is weird for binary compatibility */
+#define Py_T_CHAR      7   /* 1-character string */
+#define Py_T_BYTE      8   /* 8-bit signed int */
+/* unsigned variants: */
+#define Py_T_UBYTE     9
+#define Py_T_USHORT    10
+#define Py_T_UINT      11
+#define Py_T_ULONG     12
+
+/* Added by Jack: strings contained in the structure */
+#define Py_T_STRING_INPLACE    13
+
+/* Added by Lillo: bools contained in the structure (assumed char) */
+#define Py_T_BOOL      14
+
+#define Py_T_OBJECT_EX 16
+#define Py_T_LONGLONG  17
+#define Py_T_ULONGLONG 18
+
+#define Py_T_PYSSIZET  19      /* Py_ssize_t */
+#define _Py_T_NONE     20 // Deprecated. Value is always None.
+
+/* Flags */
+#define Py_READONLY            1
+#define Py_AUDIT_READ          2 // Added in 3.10, harmless no-op before that
+#define _Py_WRITE_RESTRICTED   4 // Deprecated, no-op. Do not reuse the value.
+
+PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *);
+PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *);
+
 #ifndef Py_LIMITED_API
 #  define Py_CPYTHON_DESCROBJECT_H
 #  include "cpython/descrobject.h"
diff --git a/Include/structmember.h b/Include/structmember.h
index 65a777d5f521..f6e8fd829892 100644
--- a/Include/structmember.h
+++ b/Include/structmember.h
@@ -5,69 +5,50 @@ extern "C" {
 #endif
 
 
-/* Interface to map C struct members to Python object attributes */
-
-#include <stddef.h> /* For offsetof */
-
-/* An array of PyMemberDef structures defines the name, type and offset
-   of selected members of a C structure.  These can be read by
-   PyMember_GetOne() and set by PyMember_SetOne() (except if their READONLY
-   flag is set).  The array must be terminated with an entry whose name
-   pointer is NULL. */
-
-struct PyMemberDef {
-    const char *name;
-    int type;
-    Py_ssize_t offset;
-    int flags;
-    const char *doc;
-};
+/* Interface to map C struct members to Python object attributes
+ *
+ * This header is deprecated: new code should not use stuff from here.
+ * New definitions are in descrobject.h.
+ *
+ * However, there's nothing wrong with old code continuing to use it,
+ * and there's not much mainenance overhead in maintaining a few aliases.
+ * So, don't be too eager to convert old code.
+ *
+ * It uses names not prefixed with Py_.
+ * It is also *not* included from Python.h and must be included individually.
+ */
+
+#include <stddef.h> /* For offsetof (not always provided by Python.h) */
 
 /* Types */
-#define T_SHORT     0
-#define T_INT       1
-#define T_LONG      2
-#define T_FLOAT     3
-#define T_DOUBLE    4
-#define T_STRING    5
-#define T_OBJECT    6
-/* XXX the ordering here is weird for binary compatibility */
-#define T_CHAR      7   /* 1-character string */
-#define T_BYTE      8   /* 8-bit signed int */
-/* unsigned variants: */
-#define T_UBYTE     9
-#define T_USHORT    10
-#define T_UINT      11
-#define T_ULONG     12
-
-/* Added by Jack: strings contained in the structure */
-#define T_STRING_INPLACE    13
-
-/* Added by Lillo: bools contained in the structure (assumed char) */
-#define T_BOOL      14
-
-#define T_OBJECT_EX 16  /* Like T_OBJECT, but raises AttributeError
-                           when the value is NULL, instead of
-                           converting to None. */
-#define T_LONGLONG      17
-#define T_ULONGLONG     18
-
-#define T_PYSSIZET      19      /* Py_ssize_t */
-#define T_NONE          20      /* Value is always None */
-
+#define T_SHORT     Py_T_SHORT
+#define T_INT       Py_T_INT
+#define T_LONG      Py_T_LONG
+#define T_FLOAT     Py_T_FLOAT
+#define T_DOUBLE    Py_T_DOUBLE
+#define T_STRING    Py_T_STRING
+#define T_OBJECT    _Py_T_OBJECT
+#define T_CHAR      Py_T_CHAR
+#define T_BYTE      Py_T_BYTE
+#define T_UBYTE     Py_T_UBYTE
+#define T_USHORT    Py_T_USHORT
+#define T_UINT      Py_T_UINT
+#define T_ULONG     Py_T_ULONG
+#define T_STRING_INPLACE    Py_T_STRING_INPLACE
+#define T_BOOL      Py_T_BOOL
+#define T_OBJECT_EX Py_T_OBJECT_EX
+#define T_LONGLONG  Py_T_LONGLONG
+#define T_ULONGLONG Py_T_ULONGLONG
+#define T_PYSSIZET  Py_T_PYSSIZET
+#define T_NONE      _Py_T_NONE
 
 /* Flags */
-#define READONLY            1
-#define READ_RESTRICTED     2
-#define PY_WRITE_RESTRICTED 4
+#define READONLY            Py_READONLY
+#define PY_AUDIT_READ        Py_AUDIT_READ
+#define READ_RESTRICTED     Py_AUDIT_READ
+#define PY_WRITE_RESTRICTED _Py_WRITE_RESTRICTED
 #define RESTRICTED          (READ_RESTRICTED | PY_WRITE_RESTRICTED)
 
-#define PY_AUDIT_READ       READ_RESTRICTED
-
-/* Current API, use this */
-PyAPI_FUNC(PyObject *) PyMember_GetOne(const char *, PyMemberDef *);
-PyAPI_FUNC(int) PyMember_SetOne(char *, PyMemberDef *, PyObject *);
-
 
 #ifdef __cplusplus
 }
diff --git a/Lib/test/test_capi/test_structmembers.py b/Lib/test/test_capi/test_structmembers.py
index 07d2f623f715..2cf46b203478 100644
--- a/Lib/test/test_capi/test_structmembers.py
+++ b/Lib/test/test_capi/test_structmembers.py
@@ -4,32 +4,42 @@
 
 # Skip this test if the _testcapi module isn't available.
 import_helper.import_module('_testcapi')
-from _testcapi import _test_structmembersType, \
-    CHAR_MAX, CHAR_MIN, UCHAR_MAX, \
-    SHRT_MAX, SHRT_MIN, USHRT_MAX, \
-    INT_MAX, INT_MIN, UINT_MAX, \
-    LONG_MAX, LONG_MIN, ULONG_MAX, \
-    LLONG_MAX, LLONG_MIN, ULLONG_MAX, \
-    PY_SSIZE_T_MAX, PY_SSIZE_T_MIN
-
-ts=_test_structmembersType(False,  # T_BOOL
-                          1,      # T_BYTE
-                          2,      # T_UBYTE
-                          3,      # T_SHORT
-                          4,      # T_USHORT
-                          5,      # T_INT
-                          6,      # T_UINT
-                          7,      # T_LONG
-                          8,      # T_ULONG
-                          23,     # T_PYSSIZET
-                          9.99999,# T_FLOAT
-                          10.1010101010, # T_DOUBLE
-                          "hi" # T_STRING_INPLACE
-                          )
-
-class ReadWriteTests(unittest.TestCase):
+from _testcapi import (_test_structmembersType_OldAPI,
+    _test_structmembersType_NewAPI,
+    CHAR_MAX, CHAR_MIN, UCHAR_MAX,
+    SHRT_MAX, SHRT_MIN, USHRT_MAX,
+    INT_MAX, INT_MIN, UINT_MAX,
+    LONG_MAX, LONG_MIN, ULONG_MAX,
+    LLONG_MAX, LLONG_MIN, ULLONG_MAX,
+    PY_SSIZE_T_MAX, PY_SSIZE_T_MIN,
+    )
+
+# There are two classes: one using <structmember.h> and another using
+# `Py_`-prefixed API. They should behave the same in Python
+
+def _make_test_object(cls):
+    return cls(False,  # T_BOOL
+               1,      # T_BYTE
+               2,      # T_UBYTE
+               3,      # T_SHORT
+               4,      # T_USHORT
+               5,      # T_INT
+               6,      # T_UINT
+               7,      # T_LONG
+               8,      # T_ULONG
+               23,     # T_PYSSIZET
+               9.99999,# T_FLOAT
+               10.1010101010, # T_DOUBLE
+               "hi",   # T_STRING_INPLACE
+               )
+
+
+class ReadWriteTests:
+    def setUp(self):
+        self.ts = _make_test_object(self.cls)
 
     def test_bool(self):
+        ts = self.ts
         ts.T_BOOL = True
         self.assertEqual(ts.T_BOOL, True)
         ts.T_BOOL = False
@@ -37,6 +47,7 @@ def test_bool(self):
         self.assertRaises(TypeError, setattr, ts, 'T_BOOL', 1)
 
     def test_byte(self):
+        ts = self.ts
         ts.T_BYTE = CHAR_MAX
         self.assertEqual(ts.T_BYTE, CHAR_MAX)
         ts.T_BYTE = CHAR_MIN
@@ -45,6 +56,7 @@ def test_byte(self):
         self.assertEqual(ts.T_UBYTE, UCHAR_MAX)
 
     def test_short(self):
+        ts = self.ts
         ts.T_SHORT = SHRT_MAX
         self.assertEqual(ts.T_SHORT, SHRT_MAX)
         ts.T_SHORT = SHRT_MIN
@@ -53,6 +65,7 @@ def test_short(self):
         self.assertEqual(ts.T_USHORT, USHRT_MAX)
 
     def test_int(self):
+        ts = self.ts
         ts.T_INT = INT_MAX
         self.assertEqual(ts.T_INT, INT_MAX)
         ts.T_INT = INT_MIN
@@ -61,6 +74,7 @@ def test_int(self):
         self.assertEqual(ts.T_UINT, UINT_MAX)
 
     def test_long(self):
+        ts = self.ts
         ts.T_LONG = LONG_MAX
         self.assertEqual(ts.T_LONG, LONG_MAX)
         ts.T_LONG = LONG_MIN
@@ -69,13 +83,17 @@ def test_long(self):
         self.assertEqual(ts.T_ULONG, ULONG_MAX)
 
     def test_py_ssize_t(self):
+        ts = self.ts
         ts.T_PYSSIZET = PY_SSIZE_T_MAX
         self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MAX)
         ts.T_PYSSIZET = PY_SSIZE_T_MIN
         self.assertEqual(ts.T_PYSSIZET, PY_SSIZE_T_MIN)
 
-    @unittest.skipUnless(hasattr(ts, "T_LONGLONG"), "long long not present")
     def test_longlong(self):
+        ts = self.ts
+        if not hasattr(ts, "T_LONGLONG"):
+            self.skipTest("long long not present")
+
         ts.T_LONGLONG = LLONG_MAX
         self.assertEqual(ts.T_LONGLONG, LLONG_MAX)
         ts.T_LONGLONG = LLONG_MIN
@@ -91,6 +109,7 @@ def test_longlong(self):
         self.assertEqual(ts.T_ULONGLONG, 4)
 
     def test_bad_assignments(self):
+        ts = self.ts
         integer_attributes = [
             'T_BOOL',
             'T_BYTE', 'T_UBYTE',
@@ -109,37 +128,57 @@ def test_bad_assignments(self):
                 self.assertRaises(TypeError, setattr, ts, attr, nonint)
 
     def test_inplace_string(self):
+        ts = self.ts
         self.assertEqual(ts.T_STRING_INPLACE, "hi")
         self.assertRaises(TypeError, setattr, ts, "T_STRING_INPLACE", "s")
         self.assertRaises(TypeError, delattr, ts, "T_STRING_INPLACE")
 
+class ReadWriteTests_OldAPI(ReadWriteTests, unittest.TestCase):
+    cls = _test_structmembersType_OldAPI
+
+class ReadWriteTests_NewAPI(ReadWriteTests, unittest.TestCase):
+    cls = _test_structmembersType_NewAPI
 
-class TestWarnings(unittest.TestCase):
+class TestWarnings:
+    def setUp(self):
+        self.ts = _make_test_object(self.cls)
 
     def test_byte_max(self):
+        ts = self.ts
         with warnings_helper.check_warnings(('', RuntimeWarning)):
             ts.T_BYTE = CHAR_MAX+1
 
     def test_byte_min(self):
+        ts = self.ts
         with warnings_helper.check_warnings(('', RuntimeWarning)):
             ts.T_BYTE = CHAR_MIN-1
 
     def test_ubyte_max(self):
+        ts = self.ts
         with warnings_helper.check_warnings(('', RuntimeWarning)):
             ts.T_UBYTE = UCHAR_MAX+1
 
     def test_short_max(self):
+        ts = self.ts
         with warnings_helper.check_warnings(('', RuntimeWarning)):
             ts.T_SHORT = SHRT_MAX+1
 
     def test_short_min(self):
+        ts = self.ts
         with warnings_helper.check_warnings(('', RuntimeWarning)):
             ts.T_SHORT = SHRT_MIN-1
 
     def test_ushort_max(self):
+        ts = self.ts
         with warnings_helper.check_warnings(('', RuntimeWarning)):
             ts.T_USHORT = USHRT_MAX+1
 
+class TestWarnings_OldAPI(TestWarnings, unittest.TestCase):
+    cls = _test_structmembersType_OldAPI
+
+class TestWarnings_NewAPI(TestWarnings, unittest.TestCase):
+    cls = _test_structmembersType_NewAPI
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst b/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst
new file mode 100644
index 000000000000..0f419427925d
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2022-11-02-16-51-24.gh-issue-47146.dsYDtI.rst	
@@ -0,0 +1,5 @@
+The ``structmember.h`` header is deprecated. Its non-deprecated contents are
+now available just by including ``Python.h``, with a ``Py_`` prefix added if
+it was missing. (Deprecated contents are :c:macro:`T_OBJECT`,
+:c:macro:`T_NONE`, and no-op flags.) Patch by Petr Viktorin, based on
+earlier work by Alexander Belopolsky and Matthias Braun.
diff --git a/Misc/stable_abi.toml b/Misc/stable_abi.toml
index 0ba0f51b2de4..aa12bcc85ceb 100644
--- a/Misc/stable_abi.toml
+++ b/Misc/stable_abi.toml
@@ -94,7 +94,7 @@
     added = '3.2'
     struct_abi_kind = 'full-abi'
 [struct.PyMemberDef]
-    added = '3.2'
+    added = '3.2'  # Before 3.12, PyMemberDef required #include "structmember.h"
     struct_abi_kind = 'full-abi'
 [struct.PyGetSetDef]
     added = '3.2'
@@ -1777,11 +1777,9 @@
     added = '3.2'
     abi_only = true
 [function.PyMember_GetOne]
-    added = '3.2'
-    abi_only = true
+    added = '3.2'  # Before 3.12, available in "structmember.h"
 [function.PyMember_SetOne]
-    added = '3.2'
-    abi_only = true
+    added = '3.2'  # Before 3.12, available in "structmember.h"
 
 # TLS api is deprecated; superseded by TSS API
 
@@ -2303,3 +2301,44 @@
     added = '3.12'
 [typedef.releasebufferproc]
     added = '3.12'
+
+[const.Py_T_BYTE]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_SHORT]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_INT]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_LONG]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_LONGLONG]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_UBYTE]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_UINT]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_USHORT]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_ULONG]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_ULONGLONG]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_PYSSIZET]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_FLOAT]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_DOUBLE]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_BOOL]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_STRING]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_STRING_INPLACE]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_CHAR]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_T_OBJECT_EX]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_READONLY]
+    added = '3.12'  # Before 3.12, available in "structmember.h" w/o Py_ prefix
+[const.Py_AUDIT_READ]
+    added = '3.12'  # Before 3.12, available in "structmember.h"
diff --git a/Modules/Setup.stdlib.in b/Modules/Setup.stdlib.in
index 7307d37bb492..d64752e8ca96 100644
--- a/Modules/Setup.stdlib.in
+++ b/Modules/Setup.stdlib.in
@@ -169,7 +169,7 @@
 @MODULE__XXTESTFUZZ_TRUE at _xxtestfuzz _xxtestfuzz/_xxtestfuzz.c _xxtestfuzz/fuzzer.c
 @MODULE__TESTBUFFER_TRUE at _testbuffer _testbuffer.c
 @MODULE__TESTINTERNALCAPI_TRUE at _testinternalcapi _testinternalcapi.c
- at MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c
+ at MODULE__TESTCAPI_TRUE@_testcapi _testcapimodule.c _testcapi/vectorcall.c _testcapi/vectorcall_limited.c _testcapi/heaptype.c _testcapi/unicode.c _testcapi/getargs.c _testcapi/pytime.c _testcapi/datetime.c _testcapi/docstring.c _testcapi/mem.c _testcapi/watchers.c _testcapi/long.c _testcapi/float.c _testcapi/structmember.c
 @MODULE__TESTCLINIC_TRUE at _testclinic _testclinic.c
 
 # Some testing modules MUST be built as shared libraries.
diff --git a/Modules/_testcapi/parts.h b/Modules/_testcapi/parts.h
index e25314a7caac..7ba3c4ebff8c 100644
--- a/Modules/_testcapi/parts.h
+++ b/Modules/_testcapi/parts.h
@@ -35,6 +35,7 @@ int _PyTestCapi_Init_Mem(PyObject *module);
 int _PyTestCapi_Init_Watchers(PyObject *module);
 int _PyTestCapi_Init_Long(PyObject *module);
 int _PyTestCapi_Init_Float(PyObject *module);
+int _PyTestCapi_Init_Structmember(PyObject *module);
 
 #ifdef LIMITED_API_AVAILABLE
 int _PyTestCapi_Init_VectorcallLimited(PyObject *module);
diff --git a/Modules/_testcapi/structmember.c b/Modules/_testcapi/structmember.c
new file mode 100644
index 000000000000..0fb872a4328d
--- /dev/null
+++ b/Modules/_testcapi/structmember.c
@@ -0,0 +1,217 @@
+#define PY_SSIZE_T_CLEAN
+#include "parts.h"
+#include <stddef.h>   // for offsetof()
+
+
+// This defines two classes that contain all the simple member types, one
+// using "new" Py_-prefixed API, and the other using "old" <structmember.h>.
+// They should behave identically in Python.
+
+typedef struct {
+    char bool_member;
+    char byte_member;
+    unsigned char ubyte_member;
+    short short_member;
+    unsigned short ushort_member;
+    int int_member;
+    unsigned int uint_member;
+    long long_member;
+    unsigned long ulong_member;
+    Py_ssize_t pyssizet_member;
+    float float_member;
+    double double_member;
+    char inplace_member[6];
+    long long longlong_member;
+    unsigned long long ulonglong_member;
+} all_structmembers;
+
+typedef struct {
+    PyObject_HEAD
+    all_structmembers structmembers;
+} test_structmembers;
+
+
+static struct PyMemberDef test_members_newapi[] = {
+    {"T_BOOL", Py_T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL},
+    {"T_BYTE", Py_T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL},
+    {"T_UBYTE", Py_T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL},
+    {"T_SHORT", Py_T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL},
+    {"T_USHORT", Py_T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL},
+    {"T_INT", Py_T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL},
+    {"T_UINT", Py_T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL},
+    {"T_LONG", Py_T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL},
+    {"T_ULONG", Py_T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL},
+    {"T_PYSSIZET", Py_T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL},
+    {"T_FLOAT", Py_T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL},
+    {"T_DOUBLE", Py_T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL},
+    {"T_STRING_INPLACE", Py_T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL},
+    {"T_LONGLONG", Py_T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL},
+    {"T_ULONGLONG", Py_T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL},
+    {NULL}
+};
+
+static PyObject *
+test_structmembers_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
+{
+    static char *keywords[] = {
+        "T_BOOL", "T_BYTE", "T_UBYTE", "T_SHORT", "T_USHORT",
+        "T_INT", "T_UINT", "T_LONG", "T_ULONG", "T_PYSSIZET",
+        "T_FLOAT", "T_DOUBLE", "T_STRING_INPLACE",
+        "T_LONGLONG", "T_ULONGLONG",
+        NULL};
+    static const char fmt[] = "|bbBhHiIlknfds#LK";
+    test_structmembers *ob;
+    const char *s = NULL;
+    Py_ssize_t string_len = 0;
+    ob = PyObject_New(test_structmembers, type);
+    if (ob == NULL) {
+        return NULL;
+    }
+    memset(&ob->structmembers, 0, sizeof(all_structmembers));
+    if (!PyArg_ParseTupleAndKeywords(args, kwargs, fmt, keywords,
+                                     &ob->structmembers.bool_member,
+                                     &ob->structmembers.byte_member,
+                                     &ob->structmembers.ubyte_member,
+                                     &ob->structmembers.short_member,
+                                     &ob->structmembers.ushort_member,
+                                     &ob->structmembers.int_member,
+                                     &ob->structmembers.uint_member,
+                                     &ob->structmembers.long_member,
+                                     &ob->structmembers.ulong_member,
+                                     &ob->structmembers.pyssizet_member,
+                                     &ob->structmembers.float_member,
+                                     &ob->structmembers.double_member,
+                                     &s, &string_len,
+                                     &ob->structmembers.longlong_member,
+                                     &ob->structmembers.ulonglong_member))
+    {
+        Py_DECREF(ob);
+        return NULL;
+    }
+    if (s != NULL) {
+        if (string_len > 5) {
+            Py_DECREF(ob);
+            PyErr_SetString(PyExc_ValueError, "string too long");
+            return NULL;
+        }
+        strcpy(ob->structmembers.inplace_member, s);
+    }
+    else {
+        strcpy(ob->structmembers.inplace_member, "");
+    }
+    return (PyObject *)ob;
+}
+
+static PyType_Slot test_structmembers_slots[] = {
+    {Py_tp_new, test_structmembers_new},
+    {Py_tp_members, test_members_newapi},
+    {0},
+};
+
+static PyType_Spec test_structmembers_spec = {
+    .name = "_testcapi._test_structmembersType_NewAPI",
+    .flags = Py_TPFLAGS_DEFAULT,
+    .basicsize = sizeof(test_structmembers),
+    .slots = test_structmembers_slots,
+};
+
+#include <structmember.h>
+
+static struct PyMemberDef test_members[] = {
+    {"T_BOOL", T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL},
+    {"T_BYTE", T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL},
+    {"T_UBYTE", T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL},
+    {"T_SHORT", T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL},
+    {"T_USHORT", T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL},
+    {"T_INT", T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL},
+    {"T_UINT", T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL},
+    {"T_LONG", T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL},
+    {"T_ULONG", T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL},
+    {"T_PYSSIZET", T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL},
+    {"T_FLOAT", T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL},
+    {"T_DOUBLE", T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL},
+    {"T_STRING_INPLACE", T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL},
+    {"T_LONGLONG", T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL},
+    {"T_ULONGLONG", T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL},
+    {NULL}
+};
+
+
+static void
+test_structmembers_free(PyObject *ob)
+{
+    PyObject_Free(ob);
+}
+
+/* Designated initializers would work too, but this does test the *old* API */
+static PyTypeObject test_structmembersType_OldAPI= {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "test_structmembersType_OldAPI",
+    sizeof(test_structmembers),         /* tp_basicsize */
+    0,                                  /* tp_itemsize */
+    test_structmembers_free,            /* destructor tp_dealloc */
+    0,                                  /* tp_vectorcall_offset */
+    0,                                  /* tp_getattr */
+    0,                                  /* tp_setattr */
+    0,                                  /* tp_as_async */
+    0,                                  /* tp_repr */
+    0,                                  /* tp_as_number */
+    0,                                  /* tp_as_sequence */
+    0,                                  /* tp_as_mapping */
+    0,                                  /* tp_hash */
+    0,                                  /* tp_call */
+    0,                                  /* tp_str */
+    PyObject_GenericGetAttr,            /* tp_getattro */
+    PyObject_GenericSetAttr,            /* tp_setattro */
+    0,                                  /* tp_as_buffer */
+    0,                                  /* tp_flags */
+    "Type containing all structmember types",
+    0,                                  /* traverseproc tp_traverse */
+    0,                                  /* tp_clear */
+    0,                                  /* tp_richcompare */
+    0,                                  /* tp_weaklistoffset */
+    0,                                  /* tp_iter */
+    0,                                  /* tp_iternext */
+    0,                                  /* tp_methods */
+    test_members,                       /* tp_members */
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    0,
+    test_structmembers_new,             /* tp_new */
+};
+
+
+int
+_PyTestCapi_Init_Structmember(PyObject *m)
+{
+    int res;
+    res = PyType_Ready(&test_structmembersType_OldAPI);
+    if (res < 0) {
+        return -1;
+    }
+    res = PyModule_AddObject(
+        m,
+        "_test_structmembersType_OldAPI",
+        (PyObject *)&test_structmembersType_OldAPI);
+    if (res < 0) {
+        return -1;
+    }
+
+    PyObject *test_structmembersType_NewAPI = PyType_FromModuleAndSpec(
+        m, &test_structmembers_spec, NULL);
+    if (!test_structmembersType_NewAPI) {
+        return -1;
+    }
+    res = PyModule_AddType(m, (PyTypeObject*)test_structmembersType_NewAPI);
+    Py_DECREF(test_structmembersType_NewAPI);
+    if (res < 0) {
+        return -1;
+    }
+
+    return 0;
+}
diff --git a/Modules/_testcapimodule.c b/Modules/_testcapimodule.c
index 9dd09f68003d..83eef73a875d 100644
--- a/Modules/_testcapimodule.c
+++ b/Modules/_testcapimodule.c
@@ -21,7 +21,7 @@
 
 #include "Python.h"
 #include "marshal.h"              // PyMarshal_WriteLongToFile
-#include "structmember.h"         // PyMemberDef
+#include "structmember.h"         // for offsetof(), T_OBJECT
 #include <float.h>                // FLT_MAX
 #include <signal.h>
 
@@ -3371,147 +3371,6 @@ static PyMethodDef TestMethods[] = {
     {NULL, NULL} /* sentinel */
 };
 
-typedef struct {
-    char bool_member;
-    char byte_member;
-    unsigned char ubyte_member;
-    short short_member;
-    unsigned short ushort_member;
-    int int_member;
-    unsigned int uint_member;
-    long long_member;
-    unsigned long ulong_member;
-    Py_ssize_t pyssizet_member;
-    float float_member;
-    double double_member;
-    char inplace_member[6];
-    long long longlong_member;
-    unsigned long long ulonglong_member;
-} all_structmembers;
-
-typedef struct {
-    PyObject_HEAD
-    all_structmembers structmembers;
-} test_structmembers;
-
-static struct PyMemberDef test_members[] = {
-    {"T_BOOL", T_BOOL, offsetof(test_structmembers, structmembers.bool_member), 0, NULL},
-    {"T_BYTE", T_BYTE, offsetof(test_structmembers, structmembers.byte_member), 0, NULL},
-    {"T_UBYTE", T_UBYTE, offsetof(test_structmembers, structmembers.ubyte_member), 0, NULL},
-    {"T_SHORT", T_SHORT, offsetof(test_structmembers, structmembers.short_member), 0, NULL},
-    {"T_USHORT", T_USHORT, offsetof(test_structmembers, structmembers.ushort_member), 0, NULL},
-    {"T_INT", T_INT, offsetof(test_structmembers, structmembers.int_member), 0, NULL},
-    {"T_UINT", T_UINT, offsetof(test_structmembers, structmembers.uint_member), 0, NULL},
-    {"T_LONG", T_LONG, offsetof(test_structmembers, structmembers.long_member), 0, NULL},
-    {"T_ULONG", T_ULONG, offsetof(test_structmembers, structmembers.ulong_member), 0, NULL},
-    {"T_PYSSIZET", T_PYSSIZET, offsetof(test_structmembers, structmembers.pyssizet_member), 0, NULL},
-    {"T_FLOAT", T_FLOAT, offsetof(test_structmembers, structmembers.float_member), 0, NULL},
-    {"T_DOUBLE", T_DOUBLE, offsetof(test_structmembers, structmembers.double_member), 0, NULL},
-    {"T_STRING_INPLACE", T_STRING_INPLACE, offsetof(test_structmembers, structmembers.inplace_member), 0, NULL},
-    {"T_LONGLONG", T_LONGLONG, offsetof(test_structmembers, structmembers.longlong_member), 0, NULL},
-    {"T_ULONGLONG", T_ULONGLONG, offsetof(test_structmembers, structmembers.ulonglong_member), 0, NULL},
-    {NULL}
-};
-
-
-static PyObject *
-test_structmembers_new(PyTypeObject *type, PyObject *args, PyObject *kwargs)
-{
-    static char *keywords[] = {
-        "T_BOOL", "T_BYTE", "T_UBYTE", "T_SHORT", "T_USHORT",
-        "T_INT", "T_UINT", "T_LONG", "T_ULONG", "T_PYSSIZET",
-        "T_FLOAT", "T_DOUBLE", "T_STRING_INPLACE",
-        "T_LONGLONG", "T_ULONGLONG",
-        NULL};
-    static const char fmt[] = "|bbBhHiIlknfds#LK";
-    test_structmembers *ob;
-    const char *s = NULL;
-    Py_ssize_t string_len = 0;
-    ob = PyObject_New(test_structmembers, type);
-    if (ob == NULL)
-        return NULL;
-    memset(&ob->structmembers, 0, sizeof(all_structmembers));
-    if (!PyArg_ParseTupleAndKeywords(args, kwargs, fmt, keywords,
-                                     &ob->structmembers.bool_member,
-                                     &ob->structmembers.byte_member,
-                                     &ob->structmembers.ubyte_member,
-                                     &ob->structmembers.short_member,
-                                     &ob->structmembers.ushort_member,
-                                     &ob->structmembers.int_member,
-                                     &ob->structmembers.uint_member,
-                                     &ob->structmembers.long_member,
-                                     &ob->structmembers.ulong_member,
-                                     &ob->structmembers.pyssizet_member,
-                                     &ob->structmembers.float_member,
-                                     &ob->structmembers.double_member,
-                                     &s, &string_len
-                                     , &ob->structmembers.longlong_member,
-                                     &ob->structmembers.ulonglong_member
-        )) {
-        Py_DECREF(ob);
-        return NULL;
-    }
-    if (s != NULL) {
-        if (string_len > 5) {
-            Py_DECREF(ob);
-            PyErr_SetString(PyExc_ValueError, "string too long");
-            return NULL;
-        }
-        strcpy(ob->structmembers.inplace_member, s);
-    }
-    else {
-        strcpy(ob->structmembers.inplace_member, "");
-    }
-    return (PyObject *)ob;
-}
-
-static void
-test_structmembers_free(PyObject *ob)
-{
-    PyObject_Free(ob);
-}
-
-static PyTypeObject test_structmembersType = {
-    PyVarObject_HEAD_INIT(NULL, 0)
-    "test_structmembersType",
-    sizeof(test_structmembers),         /* tp_basicsize */
-    0,                                  /* tp_itemsize */
-    test_structmembers_free,            /* destructor tp_dealloc */
-    0,                                  /* tp_vectorcall_offset */
-    0,                                  /* tp_getattr */
-    0,                                  /* tp_setattr */
-    0,                                  /* tp_as_async */
-    0,                                  /* tp_repr */
-    0,                                  /* tp_as_number */
-    0,                                  /* tp_as_sequence */
-    0,                                  /* tp_as_mapping */
-    0,                                  /* tp_hash */
-    0,                                  /* tp_call */
-    0,                                  /* tp_str */
-    PyObject_GenericGetAttr,            /* tp_getattro */
-    PyObject_GenericSetAttr,            /* tp_setattro */
-    0,                                  /* tp_as_buffer */
-    0,                                  /* tp_flags */
-    "Type containing all structmember types",
-    0,                                  /* traverseproc tp_traverse */
-    0,                                  /* tp_clear */
-    0,                                  /* tp_richcompare */
-    0,                                  /* tp_weaklistoffset */
-    0,                                  /* tp_iter */
-    0,                                  /* tp_iternext */
-    0,                                  /* tp_methods */
-    test_members,                       /* tp_members */
-    0,
-    0,
-    0,
-    0,
-    0,
-    0,
-    0,
-    0,
-    test_structmembers_new,             /* tp_new */
-};
-
 
 typedef struct {
     PyObject_HEAD
@@ -4064,11 +3923,6 @@ PyInit__testcapi(void)
 
     Py_SET_TYPE(&_HashInheritanceTester_Type, &PyType_Type);
 
-    Py_SET_TYPE(&test_structmembersType, &PyType_Type);
-    Py_INCREF(&test_structmembersType);
-    /* don't use a name starting with "test", since we don't want
-       test_capi to automatically call this */
-    PyModule_AddObject(m, "_test_structmembersType", (PyObject *)&test_structmembersType);
     if (PyType_Ready(&matmulType) < 0)
         return NULL;
     Py_INCREF(&matmulType);
@@ -4197,6 +4051,9 @@ PyInit__testcapi(void)
     if (_PyTestCapi_Init_Float(m) < 0) {
         return NULL;
     }
+    if (_PyTestCapi_Init_Structmember(m) < 0) {
+        return NULL;
+    }
 
 #ifndef LIMITED_API_AVAILABLE
     PyModule_AddObjectRef(m, "LIMITED_API_AVAILABLE", Py_False);
diff --git a/PCbuild/_testcapi.vcxproj b/PCbuild/_testcapi.vcxproj
index d91cdfef7b6b..58bf4e1eacbf 100644
--- a/PCbuild/_testcapi.vcxproj
+++ b/PCbuild/_testcapi.vcxproj
@@ -106,6 +106,7 @@
     <ClCompile Include="..\Modules\_testcapi\watchers.c" />
     <ClCompile Include="..\Modules\_testcapi\float.c" />
     <ClCompile Include="..\Modules\_testcapi\long.c" />
+    <ClCompile Include="..\Modules\_testcapi\structmember.c" />
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc" />
diff --git a/PCbuild/_testcapi.vcxproj.filters b/PCbuild/_testcapi.vcxproj.filters
index 1b112b164ff0..101c53227616 100644
--- a/PCbuild/_testcapi.vcxproj.filters
+++ b/PCbuild/_testcapi.vcxproj.filters
@@ -48,6 +48,9 @@
     <ClCompile Include="..\Modules\_testcapi\long.c">
       <Filter>Source Files</Filter>
     </ClCompile>
+    <ClCompile Include="..\Modules\_testcapi\structmember.c">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ResourceCompile Include="..\PC\python_nt.rc">



More information about the Python-checkins mailing list