Repository: python/cpython
Pull request #132103: GH-118761: Expose more core interpreter types in ``_types``
Author: login: AA-Turner
Labels: name: performance
description: Performance or resource usage
name: skip news
Created: 2025-04-05T00:24:38Z | Merged: 2025-04-05T18:11:07Z

This was discussed in #131969 (cc @ZeroIntensity), but deferred to get the smaller change in. This PR adds all of the core interpreter types currently exposed in the `types` module to `_typesmodule.c`. We then change the definition of `types.py` to prefer these, but if the `_types` module is not available, to use the pure-Python fallbacks.

Strictly, this isn't needed, as `_types` is a built-in module. I think it might be helpful to keep the pure Python definitions for other implementations (PyPy, etc), and perhaps as a quick reminder of what each type looks like. Open to thoughts on this, as it would clearly be simpler to replace everything with `from _types import *`.

I've initially categorised this under #118761, as this does provide a ~15% speed-up for me (1627µs to 1403µs).

A

<!-- gh-issue-number: gh-118761 -->
* Issue: gh-118761
<!-- /gh-issue-number -->



Diff:
diff --git a/Lib/test/test_types.py b/Lib/test/test_types.py
index f014f7e9ee08c9..671f396d75be70 100644
--- a/Lib/test/test_types.py
+++ b/Lib/test/test_types.py
@@ -4,6 +4,8 @@
     run_with_locale, cpython_only, no_rerun,
     MISSING_C_DOCSTRINGS,
 )
+from test.support.import_helper import import_fresh_module
+
 import collections.abc
 from collections import namedtuple, UserDict
 import copy
@@ -19,6 +21,8 @@
 import weakref
 import typing
 
+c_types = import_fresh_module('types', fresh=['_types'])
+py_types = import_fresh_module('types', blocked=['_types'])
 
 T = typing.TypeVar("T")
 
@@ -34,6 +38,28 @@ def clear_typing_caches():
 
 class TypesTests(unittest.TestCase):
 
+    def test_names(self):
+        c_only_names = {'CapsuleType'}
+        ignored = {'new_class', 'resolve_bases', 'prepare_class',
+                   'get_original_bases', 'DynamicClassAttribute', 'coroutine'}
+
+        for name in c_types.__all__:
+            if name not in c_only_names | ignored:
+                self.assertIs(getattr(c_types, name), getattr(py_types, name))
+
+        all_names = ignored | {
+            'AsyncGeneratorType', 'BuiltinFunctionType', 'BuiltinMethodType',
+            'CapsuleType', 'CellType', 'ClassMethodDescriptorType', 'CodeType',
+            'CoroutineType', 'EllipsisType', 'FrameType', 'FunctionType',
+            'GeneratorType', 'GenericAlias', 'GetSetDescriptorType',
+            'LambdaType', 'MappingProxyType', 'MemberDescriptorType',
+            'MethodDescriptorType', 'MethodType', 'MethodWrapperType',
+            'ModuleType', 'NoneType', 'NotImplementedType', 'SimpleNamespace',
+            'TracebackType', 'UnionType', 'WrapperDescriptorType',
+        }
+        self.assertEqual(all_names, set(c_types.__all__))
+        self.assertEqual(all_names - c_only_names, set(py_types.__all__))
+
     def test_truth_values(self):
         if None: self.fail('None is true instead of false')
         if 0: self.fail('0 is true instead of false')
diff --git a/Lib/types.py b/Lib/types.py
index 9aa0a3b087c1c7..6efac3394345a5 100644
--- a/Lib/types.py
+++ b/Lib/types.py
@@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
+except ImportError:
+    import sys
+
+    def _f(): pass
+    FunctionType = type(_f)
+    LambdaType = type(lambda: None)  # Same as FunctionType
+    CodeType = type(_f.__code__)
+    MappingProxyType = type(type.__dict__)
+    SimpleNamespace = type(sys.implementation)
+
+    def _cell_factory():
+        a = 1
+        def f():
+            nonlocal a
+        return f.__closure__[0]
+    CellType = type(_cell_factory())
+
+    def _g():
+        yield 1
+    GeneratorType = type(_g())
+
+    async def _c(): pass
+    _c = _c()
+    CoroutineType = type(_c)
+    _c.close()  # Prevent ResourceWarning
+
+    async def _ag():
+        yield
+    _ag = _ag()
+    AsyncGeneratorType = type(_ag)
+
+    class _C:
+        def _m(self): pass
+    MethodType = type(_C()._m)
+
+    BuiltinFunctionType = type(len)
+    BuiltinMethodType = type([].append)  # Same as BuiltinFunctionType
+
+    WrapperDescriptorType = type(object.__init__)
+    MethodWrapperType = type(object().__str__)
+    MethodDescriptorType = type(str.join)
+    ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
+
+    ModuleType = type(sys)
 
-WrapperDescriptorType = type(object.__init__)
-MethodWrapperType = type(object().__str__)
-MethodDescriptorType = type(str.join)
-ClassMethodDescriptorType = type(dict.__dict__['fromkeys'])
+    try:
+        raise TypeError
+    except TypeError as exc:
+        TracebackType = type(exc.__traceback__)
+        FrameType = type(exc.__traceback__.tb_frame)
 
-ModuleType = type(_types)
+    GetSetDescriptorType = type(FunctionType.__code__)
+    MemberDescriptorType = type(FunctionType.__globals__)
 
-try:
-    raise TypeError
-except TypeError as exc:
-    TracebackType = type(exc.__traceback__)
-    FrameType = type(exc.__traceback__.tb_frame)
+    GenericAlias = type(list[int])
+    UnionType = type(int | str)
 
-GetSetDescriptorType = type(FunctionType.__code__)
-MemberDescriptorType = type(FunctionType.__globals__)
+    EllipsisType = type(Ellipsis)
+    NoneType = type(None)
+    NotImplementedType = type(NotImplemented)
 
-CapsuleType = _types.CapsuleType
+    # CapsuleType cannot be accessed from pure Python,
+    # so there is no fallback definition.
 
-del _types, _f, _g, _C, _c, _ag, _cell_factory  # Not for export
+    del sys, _f, _g, _C, _c, _ag, _cell_factory  # Not for export
 
 
 # Provide a PEP 3115 compliant mechanism for class creation
@@ -326,11 +337,4 @@ def wrapped(*args, **kwargs):
 
     return wrapped
 
-GenericAlias = type(list[int])
-UnionType = type(int | str)
-
-EllipsisType = type(Ellipsis)
-NoneType = type(None)
-NotImplementedType = type(NotImplemented)
-
 __all__ = [n for n in globals() if not n.startswith('_')]  # for pydoc
diff --git a/Modules/_typesmodule.c b/Modules/_typesmodule.c
index aabb35f47eefc3..a30a88196e7192 100644
--- a/Modules/_typesmodule.c
+++ b/Modules/_typesmodule.c
@@ -1,17 +1,52 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"        // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"   // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        assert(PyUnstable_IsImmortal((PyObject *)&(TYPE)));              \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
+            return -1;                                                   \
+        }                                                                \
+    } while (0)
+
+    EXPORT_STATIC_TYPE("AsyncGeneratorType", PyAsyncGen_Type);
+    EXPORT_STATIC_TYPE("BuiltinFunctionType", PyCFunction_Type);
+    // BuiltinMethodType is the same as BuiltinFunctionType
+    EXPORT_STATIC_TYPE("BuiltinMethodType", PyCFunction_Type);
+    EXPORT_STATIC_TYPE("CapsuleType", PyCapsule_Type);
+    EXPORT_STATIC_TYPE("CellType", PyCell_Type);
+    EXPORT_STATIC_TYPE("ClassMethodDescriptorType", PyClassMethodDescr_Type);
+    EXPORT_STATIC_TYPE("CodeType", PyCode_Type);
+    EXPORT_STATIC_TYPE("CoroutineType", PyCoro_Type);
+    EXPORT_STATIC_TYPE("EllipsisType", PyEllipsis_Type);
+    EXPORT_STATIC_TYPE("FrameType", PyFrame_Type);
+    EXPORT_STATIC_TYPE("FunctionType", PyFunction_Type);
+    EXPORT_STATIC_TYPE("GeneratorType", PyGen_Type);
+    EXPORT_STATIC_TYPE("GenericAlias", Py_GenericAliasType);
+    EXPORT_STATIC_TYPE("GetSetDescriptorType", PyGetSetDescr_Type);
+    // LambdaType is the same as FunctionType
+    EXPORT_STATIC_TYPE("LambdaType", PyFunction_Type);
+    EXPORT_STATIC_TYPE("MappingProxyType", PyDictProxy_Type);
+    EXPORT_STATIC_TYPE("MemberDescriptorType", PyMemberDescr_Type);
+    EXPORT_STATIC_TYPE("MethodDescriptorType", PyMethodDescr_Type);
+    EXPORT_STATIC_TYPE("MethodType", PyMethod_Type);
+    EXPORT_STATIC_TYPE("MethodWrapperType", _PyMethodWrapper_Type);
+    EXPORT_STATIC_TYPE("ModuleType", PyModule_Type);
+    EXPORT_STATIC_TYPE("NoneType", _PyNone_Type);
+    EXPORT_STATIC_TYPE("NotImplementedType", _PyNotImplemented_Type);
+    EXPORT_STATIC_TYPE("SimpleNamespace", _PyNamespace_Type);
+    EXPORT_STATIC_TYPE("TracebackType", PyTraceBack_Type);
+    EXPORT_STATIC_TYPE("UnionType", _PyUnion_Type);
+    EXPORT_STATIC_TYPE("WrapperDescriptorType", PyWrapperDescr_Type);
+#undef EXPORT_STATIC_TYPE
     return 0;
 }
 


Reviews:
user: login: XuehaiPan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T06:14:38Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T08:51:49Z
user: login: XuehaiPan
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-05T09:05:15Z
user: login: AA-Turner
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T14:21:31Z
user: login: AA-Turner
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T14:23:05Z
user: login: ZeroIntensity
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T14:33:59Z
user: login: ZeroIntensity
body: It might be worth adding a `_pytypes` module to hold the pure-Python fallbacks.
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T14:41:01Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T15:34:02Z
user: login: ZeroIntensity
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T15:42:01Z
user: login: AA-Turner
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T15:59:44Z
user: login: ZeroIntensity
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T16:19:14Z
user: login: AA-Turner
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T16:33:57Z
user: login: ZeroIntensity
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T17:03:29Z
user: login: AA-Turner
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T17:05:31Z
user: login: ZeroIntensity
body: LGTM. Let's do a `_pytypes` module in a follow-up if we decide to implement the rest of the functions in C.
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-05T17:11:09Z
user: login: picnixz
body: Just some cosmetic suggesetions. Concerning the spaces before the comments, I don't think we now need to have some column-aligned comments (it's not even clear that they are aligned in the first place).
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T17:27:46Z
user: login: AA-Turner
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T17:45:43Z
user: login: picnixz
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-04-05T17:47:47Z
user: login: picnixz
state: APPROVED
author_association: MEMBER
submitted_at: 2025-04-05T17:48:20Z

Review comments:
diff_hunk: @@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
path: Lib/types.py
user: login: XuehaiPan
body: Maybe need a test to ensure this provides the exact same types as the fallback.
created_at: 2025-04-05T06:14:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,17 +1,97 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"       // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"  // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
+    if (PyModule_AddObjectRef(m, "AsyncGeneratorType", (PyObject *)&PyAsyncGen_Type) < 0) {
path: Modules/_typesmodule.c
user: login: picnixz
body: To ease readability and future additions, I would suggest the following:

```c
#define ADD_STATIC_TYPE(NAME, TYPE)                                     \
    do {                                                                \
        if (PyModule_AddObjectRef(m, NAME, (PyObject *)&TYPE) < 0) {    \
            return -1;                                                  \
        }                                                               \
    } while (0)
```

and `#undef ADD_STATIC_TYPE` just before returning from `_types_exec()`. This would become:

```c
static int
_types_exec(PyObject *m)
{
#define ADD_STATIC_TYPE(NAME, TYPE)                                     \
    do {                                                                \
        if (PyModule_AddObjectRef(m, NAME, (PyObject *)&TYPE) < 0) {    \
            return -1;                                                  \
        }                                                               \
    } while (0)
    ...
    ADD_STATIC_TYPE("BuiltinFunctionType", PyCFunction_Type);
    ...
#undef ADD_STATIC_TYPE
    return 0;
}
```
created_at: 2025-04-05T08:50:03Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,97 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"       // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"  // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
+    if (PyModule_AddObjectRef(m, "AsyncGeneratorType", (PyObject *)&PyAsyncGen_Type) < 0) {
+        return -1;
+    }
+    if (PyModule_AddObjectRef(m, "BuiltinFunctionType", (PyObject *)&PyCFunction_Type) < 0) {
+        return -1;
+    }
+    // Same as BuiltinMethodType
path: Modules/_typesmodule.c
user: login: picnixz
body: ```suggestion
    // Same as BuiltinFunctionType
```
created_at: 2025-04-05T08:50:19Z
author_association: MEMBER
diff_hunk: @@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
path: Lib/types.py
user: login: picnixz
body: Good idea, we usually test `__all__` as well so it's a good idea to check that. OTOH, we can also test what happens when the `_types` module is blocked.
created_at: 2025-04-05T08:51:46Z
author_association: MEMBER
diff_hunk: @@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
+except ImportError:
+    import sys
path: Lib/types.py
user: login: XuehaiPan
body: For test purpose, a better approach is to put all the fallbacks in a function:

```python
def _retrieve_types() -> dict[str, type]:
    import sys as _sys

    def _f(): pass
    FunctionType = type(_f)
    LambdaType = type(lambda: None)         # Same as FunctionType
    CodeType = type(_f.__code__)
    MappingProxyType = type(type.__dict__)
    SimpleNamespace = type(sys.implementation)

    ...

    _types = locals().copy()
    return {k: v for k, v in _types.items() if not k.startswith('_')}


try:
    from _types import *
except ImportError:
    globals().update(_retrieve_types())
```

Then we can use this in test:

```python
assert {name: getattr(_types, name) for name in dir(_types)} = types._retrieve_types()
```
created_at: 2025-04-05T09:05:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
+except ImportError:
+    import sys
path: Lib/types.py
user: login: AA-Turner
body: Why not use `import_fresh_module('types', blocked['_types'])`?
created_at: 2025-04-05T14:21:31Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,97 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"       // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"  // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
+    if (PyModule_AddObjectRef(m, "AsyncGeneratorType", (PyObject *)&PyAsyncGen_Type) < 0) {
path: Modules/_typesmodule.c
user: login: AA-Turner
body: I considered this and thought a macro was overkill -- but readability is a good point.
created_at: 2025-04-05T14:23:05Z
author_association: MEMBER
diff_hunk: @@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
+except ImportError:
+    import sys
path: Lib/types.py
user: login: ZeroIntensity
body: Yeah, we don't need a different function. We probably want something like this:

```py
c_types = import_fresh_module('types', fresh=['_types'])
py_types = import_fresh_module('types', blocked=['_types'])
for name in c_types.__all__:
    if name not in ignored:  # CapsuleType, SimpleNamespace, new_class, etc.
        self.assertIs(getattr(c_types, name), getattr(py_types, name))
```
created_at: 2025-04-05T14:33:59Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,51 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"       // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"  // _PyUnion_Type
path: Modules/_typesmodule.c
user: login: picnixz
body: ```suggestion
#include "pycore_descrobject.h"     // _PyMethodWrapper_Type
#include "pycore_namespace.h"       // _PyNamespace_Type
#include "pycore_object.h"          // _PyNone_Type, _PyNotImplemented_Type
#include "pycore_unionobject.h"     // _PyUnion_Type
```

(Aligned on tabs)
created_at: 2025-04-05T15:33:06Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,51 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"       // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"  // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
+            return -1;                                                   \
+        }                                                                \
+    } while (0)
+
+    EXPORT_STATIC_TYPE("AsyncGeneratorType", PyAsyncGen_Type);
+    EXPORT_STATIC_TYPE("BuiltinFunctionType", PyCFunction_Type);
+    // Same as BuiltinFunctionType
+    EXPORT_STATIC_TYPE("BuiltinMethodType", PyCFunction_Type);
+    EXPORT_STATIC_TYPE("CapsuleType", PyCapsule_Type);
+    EXPORT_STATIC_TYPE("CellType", PyCell_Type);
+    EXPORT_STATIC_TYPE("ClassMethodDescriptorType", PyClassMethodDescr_Type);
+    EXPORT_STATIC_TYPE("CodeType", PyCode_Type);
+    EXPORT_STATIC_TYPE("CoroutineType", PyCoro_Type);
+    EXPORT_STATIC_TYPE("EllipsisType", PyEllipsis_Type);
+    EXPORT_STATIC_TYPE("FrameType", PyFrame_Type);
+    EXPORT_STATIC_TYPE("FunctionType", PyFunction_Type);
+    EXPORT_STATIC_TYPE("GeneratorType", PyGen_Type);
+    EXPORT_STATIC_TYPE("GenericAlias", Py_GenericAliasType);
+    EXPORT_STATIC_TYPE("GetSetDescriptorType", PyGetSetDescr_Type);
+    // Same as FunctionType
+    EXPORT_STATIC_TYPE("LambdaType", PyFunction_Type);
+    EXPORT_STATIC_TYPE("MappingProxyType", PyDictProxy_Type);
+    EXPORT_STATIC_TYPE("MemberDescriptorType", PyMemberDescr_Type);
+    EXPORT_STATIC_TYPE("MethodDescriptorType", PyMethodDescr_Type);
+    EXPORT_STATIC_TYPE("MethodType", PyMethod_Type);
+    EXPORT_STATIC_TYPE("MethodWrapperType", _PyMethodWrapper_Type);
+    EXPORT_STATIC_TYPE("ModuleType", PyModule_Type);
+    EXPORT_STATIC_TYPE("NoneType", _PyNone_Type);
+    EXPORT_STATIC_TYPE("NotImplementedType", _PyNotImplemented_Type);
+    EXPORT_STATIC_TYPE("SimpleNamespace", _PyNamespace_Type);
+    EXPORT_STATIC_TYPE("TracebackType", PyTraceBack_Type);
+    EXPORT_STATIC_TYPE("UnionType", _PyUnion_Type);
+    EXPORT_STATIC_TYPE("WrapperDescriptorType", PyWrapperDescr_Type);
+#undef ADD_STATIC_TYPE
path: Modules/_typesmodule.c
user: login: picnixz
body: ```suggestion
#undef EXPORT_STATIC_TYPE
```
created_at: 2025-04-05T15:33:46Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,51 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"       // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"  // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
path: Modules/_typesmodule.c
user: login: ZeroIntensity
body: Let's make sure we're actually using static types:
```suggestion
        assert(PyUnstable_IsImmortal((PyObject *)&(TYPE)));
        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
```

(I think there is a `_Py_IsStaticImmortal` somewhere, but let's just use the public API here.)
created_at: 2025-04-05T15:42:01Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,51 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"       // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"  // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
path: Modules/_typesmodule.c
user: login: AA-Turner
body: Good idea (applied manually to add a backslash for the macro)
created_at: 2025-04-05T15:59:44Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,52 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"        // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"   // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        assert(PyUnstable_IsImmortal((PyObject *)&(TYPE)));              \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
+            return -1;                                                   \
+        }                                                                \
+    } while (0)
+
+    EXPORT_STATIC_TYPE("AsyncGeneratorType", PyAsyncGen_Type);
+    EXPORT_STATIC_TYPE("BuiltinFunctionType", PyCFunction_Type);
+    // Same as BuiltinFunctionType
+    EXPORT_STATIC_TYPE("BuiltinMethodType", PyCFunction_Type);
path: Modules/_typesmodule.c
user: login: ZeroIntensity
body: I think this is one of the few times where it's better to put the comment on the same line. It's not immediately clear to me that the comment is referring to `BuiltinMethodType`.
```suggestion
    EXPORT_STATIC_TYPE("BuiltinMethodType", PyCFunction_Type); // Same as BuiltinFunctionType
```

Same goes for `FunctionType` below.
created_at: 2025-04-05T16:19:14Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,52 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"        // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"   // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        assert(PyUnstable_IsImmortal((PyObject *)&(TYPE)));              \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
+            return -1;                                                   \
+        }                                                                \
+    } while (0)
+
+    EXPORT_STATIC_TYPE("AsyncGeneratorType", PyAsyncGen_Type);
+    EXPORT_STATIC_TYPE("BuiltinFunctionType", PyCFunction_Type);
+    // Same as BuiltinFunctionType
+    EXPORT_STATIC_TYPE("BuiltinMethodType", PyCFunction_Type);
path: Modules/_typesmodule.c
user: login: AA-Turner
body: What do you think about [`0fbbf0e` (#132103)](https://github.com/python/cpython/pull/132103/commits/0fbbf0ec14d0925d2281f05d47882223acf1742b)? I can't find a nice way of putting the LambdaType comment inline without breaking line length.

A
created_at: 2025-04-05T16:33:57Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,52 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"        // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"   // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        assert(PyUnstable_IsImmortal((PyObject *)&(TYPE)));              \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
+            return -1;                                                   \
+        }                                                                \
+    } while (0)
+
+    EXPORT_STATIC_TYPE("AsyncGeneratorType", PyAsyncGen_Type);
+    EXPORT_STATIC_TYPE("BuiltinFunctionType", PyCFunction_Type);
+    // Same as BuiltinFunctionType
+    EXPORT_STATIC_TYPE("BuiltinMethodType", PyCFunction_Type);
path: Modules/_typesmodule.c
user: login: ZeroIntensity
body: I don't think the line-length is that important here, but the new comment works too.
created_at: 2025-04-05T17:03:29Z
author_association: MEMBER
diff_hunk: @@ -1,17 +1,52 @@
 /* _types module */
 
 #include "Python.h"
+#include "pycore_descrobject.h"   // _PyMethodWrapper_Type
 #include "pycore_namespace.h"     // _PyNamespace_Type
+#include "pycore_object.h"        // _PyNone_Type, _PyNotImplemented_Type
+#include "pycore_unionobject.h"   // _PyUnion_Type
 
 static int
 _types_exec(PyObject *m)
 {
-    if (PyModule_AddObjectRef(m, "CapsuleType", (PyObject *)&PyCapsule_Type) < 0) {
-        return -1;
-    }
-    if (PyModule_AddObjectRef(m, "SimpleNamespace", (PyObject *)&_PyNamespace_Type) < 0) {
-        return -1;
-    }
+#define EXPORT_STATIC_TYPE(NAME, TYPE)                                   \
+    do {                                                                 \
+        assert(PyUnstable_IsImmortal((PyObject *)&(TYPE)));              \
+        if (PyModule_AddObjectRef(m, (NAME), (PyObject *)&(TYPE)) < 0) { \
+            return -1;                                                   \
+        }                                                                \
+    } while (0)
+
+    EXPORT_STATIC_TYPE("AsyncGeneratorType", PyAsyncGen_Type);
+    EXPORT_STATIC_TYPE("BuiltinFunctionType", PyCFunction_Type);
+    // Same as BuiltinFunctionType
+    EXPORT_STATIC_TYPE("BuiltinMethodType", PyCFunction_Type);
path: Modules/_typesmodule.c
user: login: AA-Turner
body: [PEP 7](https://peps.python.org/pep-0007/#code-lay-out) says otherwise :)
created_at: 2025-04-05T17:05:31Z
author_association: MEMBER
diff_hunk: @@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
+except ImportError:
+    import sys
+
+    def _f(): pass
+    FunctionType = type(_f)
+    LambdaType = type(lambda: None)         # Same as FunctionType
path: Lib/types.py
user: login: picnixz
body: ```suggestion
    LambdaType = type(lambda: None)  # Same as FunctionType
```
created_at: 2025-04-05T17:18:09Z
author_association: MEMBER
diff_hunk: @@ -2,67 +2,78 @@
 Define names for built-in types that aren't directly accessible as a builtin.
 """
 
-import _types
-
 # Iterators in Python aren't a matter of type but of protocol.  A large
 # and changing number of builtin types implement *some* flavor of
 # iterator.  Don't check the type!  Use hasattr to check for both
 # "__iter__" and "__next__" attributes instead.
 
-def _f(): pass
-FunctionType = type(_f)
-LambdaType = type(lambda: None)         # Same as FunctionType
-CodeType = type(_f.__code__)
-MappingProxyType = type(type.__dict__)
-SimpleNamespace = _types.SimpleNamespace
-
-def _cell_factory():
-    a = 1
-    def f():
-        nonlocal a
-    return f.__closure__[0]
-CellType = type(_cell_factory())
-
-def _g():
-    yield 1
-GeneratorType = type(_g())
-
-async def _c(): pass
-_c = _c()
-CoroutineType = type(_c)
-_c.close()  # Prevent ResourceWarning
-
-async def _ag():
-    yield
-_ag = _ag()
-AsyncGeneratorType = type(_ag)
-
-class _C:
-    def _m(self): pass
-MethodType = type(_C()._m)
-
-BuiltinFunctionType = type(len)
-BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
+try:
+    from _types import *
+except ImportError:
+    import sys
+
+    def _f(): pass
+    FunctionType = type(_f)
+    LambdaType = type(lambda: None)         # Same as FunctionType
+    CodeType = type(_f.__code__)
+    MappingProxyType = type(type.__dict__)
+    SimpleNamespace = type(sys.implementation)
+
+    def _cell_factory():
+        a = 1
+        def f():
+            nonlocal a
+        return f.__closure__[0]
+    CellType = type(_cell_factory())
+
+    def _g():
+        yield 1
+    GeneratorType = type(_g())
+
+    async def _c(): pass
+    _c = _c()
+    CoroutineType = type(_c)
+    _c.close()  # Prevent ResourceWarning
+
+    async def _ag():
+        yield
+    _ag = _ag()
+    AsyncGeneratorType = type(_ag)
+
+    class _C:
+        def _m(self): pass
+    MethodType = type(_C()._m)
+
+    BuiltinFunctionType = type(len)
+    BuiltinMethodType = type([].append)     # Same as BuiltinFunctionType
path: Lib/types.py
user: login: picnixz
body: ```suggestion
    BuiltinMethodType = type([].append)  # Same as BuiltinFunctionType
```
created_at: 2025-04-05T17:18:20Z
author_association: MEMBER
diff_hunk: @@ -34,6 +38,28 @@ def clear_typing_caches():
 
 class TypesTests(unittest.TestCase):
 
+    def test_names(self):
path: Lib/test/test_types.py
user: login: picnixz
body: I don't remember how we usually call those test methods, but maybe `test_exported_names`?
created_at: 2025-04-05T17:25:37Z
author_association: MEMBER
diff_hunk: @@ -34,6 +38,28 @@ def clear_typing_caches():
 
 class TypesTests(unittest.TestCase):
 
+    def test_names(self):
path: Lib/test/test_types.py
user: login: AA-Turner
body: There's no pattern: `test_all`, `TestImportStar`, `test_module_attributes`, `test__all__`, `test_check_all`, `test_names`, etc. 
created_at: 2025-04-05T17:45:43Z
author_association: MEMBER
diff_hunk: @@ -34,6 +38,28 @@ def clear_typing_caches():
 
 class TypesTests(unittest.TestCase):
 
+    def test_names(self):
path: Lib/test/test_types.py
user: login: picnixz
body: Oh I see. Nvm then, pick the one you want :')
created_at: 2025-04-05T17:47:47Z
author_association: MEMBER

PR comments:
user: login: AA-Turner
created_at: 2025-04-05T15:12:22Z
body: > It might be worth adding a `_pytypes` module to hold the pure-Python fallbacks.

As in the following?

```python
try:
    from _types import *
except ImportError:
    from _pytypes import *
```

Could you elaborate on the benefits?
author_association: MEMBER
user: login: picnixz
created_at: 2025-04-05T15:34:55Z
body: > Could you elaborate on the benefits?

One benefit is that it reduces by one the indentation level and it's easier to read/modify I think. Also, we do it for `_pydecimal` so why not doing it for `_pytypes`?
author_association: MEMBER
user: login: ZeroIntensity
created_at: 2025-04-05T15:40:12Z
body: > Could you elaborate on the benefits?

Yeah, what Bénédikt said. It seems cleaner to encapsulate each implementation.
author_association: MEMBER
user: login: AA-Turner
created_at: 2025-04-05T15:46:52Z
body: > Also, we do it for `_pydecimal` so why not doing it for `_pytypes`?

`_pydecimal`, `_pyio`, etc are Python implementations of the C accelarator modules (see PEP-0399), whereas `types` still contains quite a lot only implemented in Python. See e.g. `collections` and `functools` which use extension modules, but don't have a `_py<mod>` helper module.

A
author_association: MEMBER
user: login: ZeroIntensity
created_at: 2025-04-05T16:17:33Z
body: I don't have much of a preference, it's just prettier. Are we planning to implement the other functions in C?
author_association: MEMBER
user: login: AA-Turner
created_at: 2025-04-05T16:36:28Z
body: > Are we planning to implement the other functions in C?

I'm not personally, though Yury left a TODO comment by `_GeneratorWrapper` about implementing it in C. I'd probably prefer to keep the simpler functions in Python, but there might be benefits I haven't considered to converting to C.

A
author_association: MEMBER