MNT: move static data structs into their own file
diff --git a/numpy/_core/meson.build b/numpy/_core/meson.build
index dbe76e0..aba0b82 100644
--- a/numpy/_core/meson.build
+++ b/numpy/_core/meson.build
@@ -1101,6 +1101,7 @@
   'src/multiarray/nditer_constr.c',
   'src/multiarray/nditer_pywrap.c',
   src_file.process('src/multiarray/nditer_templ.c.src'),
+  'src/multiarray/npy_static_data.c',
   'src/multiarray/number.c',
   'src/multiarray/refcount.c',
   src_file.process('src/multiarray/scalartypes.c.src'),
diff --git a/numpy/_core/src/common/binop_override.h b/numpy/_core/src/common/binop_override.h
index ec3d046..def9b89 100644
--- a/numpy/_core/src/common/binop_override.h
+++ b/numpy/_core/src/common/binop_override.h
@@ -6,6 +6,7 @@
 #include "numpy/arrayobject.h"
 
 #include "get_attr_string.h"
+#include "npy_static_data.h"
 
 /*
  * Logic for deciding when binops should return NotImplemented versus when
@@ -128,7 +129,7 @@
      * Classes with __array_ufunc__ are living in the future, and only need to
      * check whether __array_ufunc__ equals None.
      */
-    attr = PyArray_LookupSpecial(other, npy_um_str_array_ufunc);
+    attr = PyArray_LookupSpecial(other, npy_interned_str.array_ufunc);
     if (attr != NULL) {
         defer = !inplace && (attr == Py_None);
         Py_DECREF(attr);
diff --git a/numpy/_core/src/common/npy_cpu_dispatch.c b/numpy/_core/src/common/npy_cpu_dispatch.c
index 388ad25..ff22f23 100644
--- a/numpy/_core/src/common/npy_cpu_dispatch.c
+++ b/numpy/_core/src/common/npy_cpu_dispatch.c
@@ -3,7 +3,7 @@
 
 #include "npy_cpu_dispatch.h"
 #include "numpy/ndarraytypes.h"
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 
 NPY_VISIBILITY_HIDDEN int
 npy_cpu_dispatch_tracer_init(PyObject *mod)
diff --git a/numpy/_core/src/common/ufunc_override.c b/numpy/_core/src/common/ufunc_override.c
index fe9ada5..17b678e 100644
--- a/numpy/_core/src/common/ufunc_override.c
+++ b/numpy/_core/src/common/ufunc_override.c
@@ -7,7 +7,7 @@
 #include "npy_import.h"
 #include "ufunc_override.h"
 #include "scalartypes.h"
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 
 /*
  * Check whether an object has __array_ufunc__ defined on its class and it
@@ -35,7 +35,7 @@
      * Does the class define __array_ufunc__? (Note that LookupSpecial has fast
      * return for basic python types, so no need to worry about those here)
      */
-    cls_array_ufunc = PyArray_LookupSpecial(obj, npy_um_str_array_ufunc);
+    cls_array_ufunc = PyArray_LookupSpecial(obj, npy_interned_str.array_ufunc);
     if (cls_array_ufunc == NULL) {
         if (PyErr_Occurred()) {
             PyErr_Clear(); /* TODO[gh-14801]: propagate crashes during attribute access? */
diff --git a/numpy/_core/src/multiarray/alloc.c b/numpy/_core/src/multiarray/alloc.c
index 8f443c6..b7e7c99 100644
--- a/numpy/_core/src/multiarray/alloc.c
+++ b/numpy/_core/src/multiarray/alloc.c
@@ -11,6 +11,7 @@
 #include "numpy/npy_common.h"
 #include "npy_config.h"
 #include "alloc.h"
+#include "npy_static_data.h"
 #include "multiarraymodule.h"
 
 #include <assert.h>
diff --git a/numpy/_core/src/multiarray/array_converter.c b/numpy/_core/src/multiarray/array_converter.c
index 4ed959c..4961730 100644
--- a/numpy/_core/src/multiarray/array_converter.c
+++ b/numpy/_core/src/multiarray/array_converter.c
@@ -21,7 +21,7 @@
 #include "abstractdtypes.h"
 #include "convert_datatype.h"
 #include "descriptor.h"
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 #include "ctors.h"
 
 #include "npy_config.h"
diff --git a/numpy/_core/src/multiarray/arrayfunction_override.c b/numpy/_core/src/multiarray/arrayfunction_override.c
index 250b02b..aa3ab42 100644
--- a/numpy/_core/src/multiarray/arrayfunction_override.c
+++ b/numpy/_core/src/multiarray/arrayfunction_override.c
@@ -7,6 +7,7 @@
 #include "numpy/ndarraytypes.h"
 #include "get_attr_string.h"
 #include "npy_import.h"
+#include "npy_static_data.h"
 #include "multiarraymodule.h"
 
 #include "arrayfunction_override.h"
diff --git a/numpy/_core/src/multiarray/arraytypes.c.src b/numpy/_core/src/multiarray/arraytypes.c.src
index b99598b..9524be8 100644
--- a/numpy/_core/src/multiarray/arraytypes.c.src
+++ b/numpy/_core/src/multiarray/arraytypes.c.src
@@ -42,6 +42,7 @@
 #include "arraytypes.h"
 
 #include "umathmodule.h"
+#include "npy_static_data.h"
 
 /*
  * Define a stack allocated dummy array with only the minimum information set:
diff --git a/numpy/_core/src/multiarray/arraywrap.c b/numpy/_core/src/multiarray/arraywrap.c
index 7b2bcd9..ae7b6e9 100644
--- a/numpy/_core/src/multiarray/arraywrap.c
+++ b/numpy/_core/src/multiarray/arraywrap.c
@@ -12,7 +12,7 @@
 #include "get_attr_string.h"
 
 #include "arraywrap.h"
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 
 
 /*
diff --git a/numpy/_core/src/multiarray/common.h b/numpy/_core/src/multiarray/common.h
index c5bb694..19fba9e 100644
--- a/numpy/_core/src/multiarray/common.h
+++ b/numpy/_core/src/multiarray/common.h
@@ -8,7 +8,7 @@
 #include "npy_cpu_dispatch.h"
 #include "numpy/npy_cpu.h"
 
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 #include "npy_import.h"
 #include <limits.h>
 
diff --git a/numpy/_core/src/multiarray/common_dtype.c b/numpy/_core/src/multiarray/common_dtype.c
index 6635f13..beba6ac 100644
--- a/numpy/_core/src/multiarray/common_dtype.c
+++ b/numpy/_core/src/multiarray/common_dtype.c
@@ -10,7 +10,7 @@
 #include "convert_datatype.h"
 #include "dtypemeta.h"
 #include "abstractdtypes.h"
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 
 
 /*
diff --git a/numpy/_core/src/multiarray/conversion_utils.c b/numpy/_core/src/multiarray/conversion_utils.c
index 9f86842..e7b1936 100644
--- a/numpy/_core/src/multiarray/conversion_utils.c
+++ b/numpy/_core/src/multiarray/conversion_utils.c
@@ -18,6 +18,7 @@
 #include "conversion_utils.h"
 #include "alloc.h"
 #include "npy_buffer.h"
+#include "npy_static_data.h"
 #include "multiarraymodule.h"
 
 static int
diff --git a/numpy/_core/src/multiarray/convert_datatype.c b/numpy/_core/src/multiarray/convert_datatype.c
index 012b2cf..f029ad8 100644
--- a/numpy/_core/src/multiarray/convert_datatype.c
+++ b/numpy/_core/src/multiarray/convert_datatype.c
@@ -35,7 +35,8 @@
 #include "dtype_transfer.h"
 #include "dtype_traversal.h"
 #include "arrayobject.h"
-
+#include "npy_static_data.h"
+#include "multiarraymodule.h"
 
 /*
  * Required length of string when converting from unsigned integer type.
diff --git a/numpy/_core/src/multiarray/ctors.c b/numpy/_core/src/multiarray/ctors.c
index 97fb3a4..8ee9d28 100644
--- a/numpy/_core/src/multiarray/ctors.c
+++ b/numpy/_core/src/multiarray/ctors.c
@@ -16,7 +16,7 @@
 #include "npy_pycompat.h"
 #include "npy_ctypes.h"
 
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 
 #include "common.h"
 #include "ctors.h"
diff --git a/numpy/_core/src/multiarray/descriptor.c b/numpy/_core/src/multiarray/descriptor.c
index 0d1debd..c1288cd 100644
--- a/numpy/_core/src/multiarray/descriptor.c
+++ b/numpy/_core/src/multiarray/descriptor.c
@@ -22,6 +22,7 @@
 #include "conversion_utils.h"  /* for PyArray_TypestrConvert */
 #include "templ_common.h" /* for npy_mul_sizes_with_overflow */
 #include "descriptor.h"
+#include "npy_static_data.h"
 #include "multiarraymodule.h"
 #include "alloc.h"
 #include "assert.h"
diff --git a/numpy/_core/src/multiarray/dlpack.c b/numpy/_core/src/multiarray/dlpack.c
index 8f1ab72..51cb454 100644
--- a/numpy/_core/src/multiarray/dlpack.c
+++ b/numpy/_core/src/multiarray/dlpack.c
@@ -8,7 +8,7 @@
 #include "numpy/arrayobject.h"
 #include "npy_argparse.h"
 #include "npy_dlpack.h"
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 #include "conversion_utils.h"
 
 
diff --git a/numpy/_core/src/multiarray/dtypemeta.c b/numpy/_core/src/multiarray/dtypemeta.c
index a21a441..87a69d8 100644
--- a/numpy/_core/src/multiarray/dtypemeta.c
+++ b/numpy/_core/src/multiarray/dtypemeta.c
@@ -26,6 +26,8 @@
 #include "templ_common.h"
 #include "refcount.h"
 #include "dtype_traversal.h"
+#include "npy_static_data.h"
+#include "multiarraymodule.h"
 
 #include <assert.h>
 
diff --git a/numpy/_core/src/multiarray/getset.c b/numpy/_core/src/multiarray/getset.c
index df2e101..092ac65 100644
--- a/numpy/_core/src/multiarray/getset.c
+++ b/numpy/_core/src/multiarray/getset.c
@@ -25,6 +25,7 @@
 #include "alloc.h"
 #include "npy_buffer.h"
 #include "shape.h"
+#include "multiarraymodule.h"
 
 /*******************  array attribute get and set routines ******************/
 
diff --git a/numpy/_core/src/multiarray/item_selection.c b/numpy/_core/src/multiarray/item_selection.c
index b954f7a..4d98ce0 100644
--- a/numpy/_core/src/multiarray/item_selection.c
+++ b/numpy/_core/src/multiarray/item_selection.c
@@ -15,7 +15,7 @@
 
 
 
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 #include "common.h"
 #include "dtype_transfer.h"
 #include "dtypemeta.h"
diff --git a/numpy/_core/src/multiarray/methods.c b/numpy/_core/src/multiarray/methods.c
index 225e13e..dd40fc4 100644
--- a/numpy/_core/src/multiarray/methods.c
+++ b/numpy/_core/src/multiarray/methods.c
@@ -29,6 +29,7 @@
 #include "strfuncs.h"
 #include "array_assign.h"
 #include "npy_dlpack.h"
+#include "npy_static_data.h"
 #include "multiarraymodule.h"
 
 #include "methods.h"
diff --git a/numpy/_core/src/multiarray/methods.h b/numpy/_core/src/multiarray/methods.h
index f70af8e..f49e020 100644
--- a/numpy/_core/src/multiarray/methods.h
+++ b/numpy/_core/src/multiarray/methods.h
@@ -1,7 +1,7 @@
 #ifndef NUMPY_CORE_SRC_MULTIARRAY_METHODS_H_
 #define NUMPY_CORE_SRC_MULTIARRAY_METHODS_H_
 
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 #include "npy_import.h"
 
 extern NPY_NO_EXPORT PyMethodDef array_methods[];
diff --git a/numpy/_core/src/multiarray/multiarraymodule.c b/numpy/_core/src/multiarray/multiarraymodule.c
index ebd165a..c6938ad 100644
--- a/numpy/_core/src/multiarray/multiarraymodule.c
+++ b/numpy/_core/src/multiarray/multiarraymodule.c
@@ -23,6 +23,7 @@
 #include "numpy/arrayobject.h"
 #include "numpy/arrayscalars.h"
 
+#include "multiarraymodule.h"
 #include "numpy/npy_math.h"
 #include "npy_argparse.h"
 #include "npy_config.h"
@@ -63,7 +64,7 @@
 #include "ctors.h"
 #include "array_assign.h"
 #include "common.h"
-#include "multiarraymodule.h"
+#include "npy_static_data.h"
 #include "cblasfuncs.h"
 #include "vdot.h"
 #include "templ_common.h" /* for npy_mul_sizes_with_overflow */
@@ -4766,193 +4767,11 @@
     return;
 }
 
-// static variables are zero-filled by default, no need to explicitly do so
-NPY_VISIBILITY_HIDDEN npy_interned_str_struct npy_interned_str;
-NPY_VISIBILITY_HIDDEN npy_static_pydata_struct npy_static_pydata;
-NPY_VISIBILITY_HIDDEN npy_static_cdata_struct npy_static_cdata;
+// static variables are automatically zero-initialized
 NPY_VISIBILITY_HIDDEN npy_thread_unsafe_state_struct npy_thread_unsafe_state;
 
 static int
-intern_strings(void)
-{
-    // this is module-level global heap allocation, it is currently
-    // never freed
-    npy_interned_str.current_allocator = PyUnicode_InternFromString("current_allocator");
-    if (npy_interned_str.current_allocator == NULL) {
-        return -1;
-    }
-    npy_interned_str.array = PyUnicode_InternFromString("__array__");
-    if (npy_interned_str.array == NULL) {
-        return -1;
-    }
-    npy_interned_str.array_function = PyUnicode_InternFromString("__array_function__");
-    if (npy_interned_str.array_function == NULL) {
-        return -1;
-    }
-    npy_interned_str.array_struct = PyUnicode_InternFromString("__array_struct__");
-    if (npy_interned_str.array_struct == NULL) {
-        return -1;
-    }
-    npy_interned_str.array_priority = PyUnicode_InternFromString("__array_priority__");
-    if (npy_interned_str.array_priority == NULL) {
-        return -1;
-    }
-    npy_interned_str.array_interface = PyUnicode_InternFromString("__array_interface__");
-    if (npy_interned_str.array_interface == NULL) {
-        return -1;
-    }
-    npy_interned_str.array_wrap = PyUnicode_InternFromString("__array_wrap__");
-    if (npy_interned_str.array_wrap == NULL) {
-        return -1;
-    }
-    npy_interned_str.array_finalize = PyUnicode_InternFromString("__array_finalize__");
-    if (npy_interned_str.array_finalize == NULL) {
-        return -1;
-    }
-    npy_interned_str.implementation = PyUnicode_InternFromString("_implementation");
-    if (npy_interned_str.implementation == NULL) {
-        return -1;
-    }
-    npy_interned_str.axis1 = PyUnicode_InternFromString("axis1");
-    if (npy_interned_str.axis1 == NULL) {
-        return -1;
-    }
-    npy_interned_str.axis2 = PyUnicode_InternFromString("axis2");
-    if (npy_interned_str.axis2 == NULL) {
-        return -1;
-    }
-    npy_interned_str.like = PyUnicode_InternFromString("like");
-    if (npy_interned_str.like == NULL) {
-        return -1;
-    }
-    npy_interned_str.numpy = PyUnicode_InternFromString("numpy");
-    if (npy_interned_str.numpy == NULL) {
-        return -1;
-    }
-    npy_interned_str.where = PyUnicode_InternFromString("where");
-    if (npy_interned_str.where == NULL) {
-        return -1;
-    }
-    /* scalar policies */
-    npy_interned_str.convert = PyUnicode_InternFromString("convert");
-    if (npy_interned_str.convert == NULL) {
-        return -1;
-    }
-    npy_interned_str.preserve = PyUnicode_InternFromString("preserve");
-    if (npy_interned_str.preserve == NULL) {
-        return -1;
-    }
-    npy_interned_str.convert_if_no_array = PyUnicode_InternFromString("convert_if_no_array");
-    if (npy_interned_str.convert_if_no_array == NULL) {
-        return -1;
-    }
-    npy_interned_str.cpu = PyUnicode_InternFromString("cpu");
-    if (npy_interned_str.cpu == NULL) {
-        return -1;
-    }
-    npy_interned_str.dtype = PyUnicode_InternFromString("dtype");
-    if (npy_interned_str.dtype == NULL) {
-        return -1;
-    }
-    npy_interned_str.array_err_msg_substr = PyUnicode_InternFromString(
-            "__array__() got an unexpected keyword argument 'copy'");
-    if (npy_interned_str.array_err_msg_substr == NULL) {
-        return -1;
-    }
-    npy_interned_str.out = PyUnicode_InternFromString("out");
-    if (npy_interned_str.out == NULL) {
-        return -1;
-    }
-    npy_interned_str.__dlpack__ = PyUnicode_InternFromString("__dlpack__");
-    if (npy_interned_str.__dlpack__ == NULL) {
-        return -1;
-    }
-    return 0;
-}
-
-#define IMPORT_GLOBAL(base_path, name, object)  \
-    assert(object == NULL);                     \
-    npy_cache_import(base_path, name, &object); \
-    if (object == NULL) {                       \
-        return -1;                              \
-    }
-
-/*
- * Initializes global constants.
- *
- * All global constants should live inside the npy_static_pydata
- * struct.
- *
- * Not all entries in the struct are initialized here, some are
- * initialized later but care must be taken in those cases to initialize
- * the constant in a thread-safe manner, ensuring it is initialized
- * exactly once.
- *
- * Anything initialized here is initialized during module import which
- * the python interpreter ensures is done in a single thread.
- *
- * Anything imported here should not need the C-layer at all and will be
- * imported before anything on the C-side is initialized.
- */
-static int
-initialize_static_globals(void)
-{
-    // cached reference to objects defined in python
-
-    IMPORT_GLOBAL("math", "floor",
-                  npy_static_pydata.math_floor_func);
-
-    IMPORT_GLOBAL("math", "ceil",
-                  npy_static_pydata.math_ceil_func);
-
-    IMPORT_GLOBAL("math", "trunc",
-                  npy_static_pydata.math_trunc_func);
-
-    IMPORT_GLOBAL("math", "gcd",
-                  npy_static_pydata.math_gcd_func);
-
-    IMPORT_GLOBAL("numpy.exceptions", "AxisError",
-                  npy_static_pydata.AxisError);
-
-    IMPORT_GLOBAL("numpy.exceptions", "ComplexWarning",
-                  npy_static_pydata.ComplexWarning);
-
-    IMPORT_GLOBAL("numpy.exceptions", "DTypePromotionError",
-                  npy_static_pydata.DTypePromotionError);
-
-    IMPORT_GLOBAL("numpy.exceptions", "TooHardError",
-                  npy_static_pydata.TooHardError);
-
-    IMPORT_GLOBAL("numpy.exceptions", "VisibleDeprecationWarning",
-                  npy_static_pydata.VisibleDeprecationWarning);
-
-    IMPORT_GLOBAL("numpy._globals", "_CopyMode",
-                  npy_static_pydata._CopyMode);
-
-    IMPORT_GLOBAL("numpy._globals", "_NoValue",
-                  npy_static_pydata._NoValue);
-
-    IMPORT_GLOBAL("numpy._core._exceptions", "_ArrayMemoryError",
-                  npy_static_pydata._ArrayMemoryError);
-
-    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncBinaryResolutionError",
-                  npy_static_pydata._UFuncBinaryResolutionError);
-
-    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncInputCastingError",
-                  npy_static_pydata._UFuncInputCastingError);
-
-    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncNoLoopError",
-                  npy_static_pydata._UFuncNoLoopError);
-
-    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncOutputCastingError",
-                  npy_static_pydata._UFuncOutputCastingError);
-
-    IMPORT_GLOBAL("os", "fspath",
-                  npy_static_pydata.os_fspath);
-
-    IMPORT_GLOBAL("os", "PathLike",
-                  npy_static_pydata.os_PathLike);
-
+initialize_thread_unsafe_state(void) {
     char *env = getenv("NUMPY_WARN_IF_NO_MEM_POLICY");
     if ((env != NULL) && (strncmp(env, "1", 1) == 0)) {
         numpy_warn_if_no_mem_policy = 1;
diff --git a/numpy/_core/src/multiarray/multiarraymodule.h b/numpy/_core/src/multiarray/multiarraymodule.h
index 9da928c..f03c264 100644
--- a/numpy/_core/src/multiarray/multiarraymodule.h
+++ b/numpy/_core/src/multiarray/multiarraymodule.h
@@ -1,147 +1,6 @@
 #ifndef NUMPY_CORE_SRC_MULTIARRAY_MULTIARRAYMODULE_H_
 #define NUMPY_CORE_SRC_MULTIARRAY_MULTIARRAYMODULE_H_
 
-typedef struct npy_interned_str_struct {
-    PyObject *current_allocator;
-    PyObject *array;
-    PyObject *array_function;
-    PyObject *array_struct;
-    PyObject *array_priority;
-    PyObject *array_interface;
-    PyObject *array_wrap;
-    PyObject *array_finalize;
-    PyObject *implementation;
-    PyObject *axis1;
-    PyObject *axis2;
-    PyObject *like;
-    PyObject *numpy;
-    PyObject *where;
-    PyObject *convert;
-    PyObject *preserve;
-    PyObject *convert_if_no_array;
-    PyObject *cpu;
-    PyObject *dtype;
-    PyObject *array_err_msg_substr;
-    PyObject *out;
-    PyObject *errmode_strings[6];
-    PyObject *__dlpack__;
-} npy_interned_str_struct;
-
-/*
- * A struct that stores static global data used throughout
- * _multiarray_umath, mostly to cache results that would be
- * prohibitively expensive to compute at runtime in a tight loop.
- *
- * All items in this struct should be initialized during module
- * initialization and thereafter should be immutable. Mutating items in
- * this struct after module initialization is likely not thread-safe.
- */
-
-typedef struct npy_static_pydata_struct {
-    /*
-     * Used in ufunc_type_resolution.c to avoid reconstructing a tuple
-     * storing the default true division return types.
-     */
-    PyObject *default_truediv_type_tup;
-
-    /*
-     * Used to set up the default extobj context variable
-     */
-    PyObject *default_extobj_capsule;
-
-    /*
-     * The global ContextVar to store the extobject. It is exposed to Python
-     * as `_extobj_contextvar`.
-     */
-    PyObject *npy_extobj_contextvar;
-
-    /*
-     * A reference to ndarray's implementations for __array_*__ special methods
-     */
-    PyObject *ndarray_array_ufunc;
-    PyObject *ndarray_array_finalize;
-    PyObject *ndarray_array_function;
-
-    /*
-     * References to the '1' and '0' PyLong objects
-     */
-    PyObject *one_obj;
-    PyObject *zero_obj;
-
-    /*
-     * References to items obtained via an import at module initialization
-     */
-    PyObject *AxisError;
-    PyObject *ComplexWarning;
-    PyObject *DTypePromotionError;
-    PyObject *TooHardError;
-    PyObject *VisibleDeprecationWarning;
-    PyObject *_CopyMode;
-    PyObject *_NoValue;
-    PyObject *_ArrayMemoryError;
-    PyObject *_UFuncBinaryResolutionError;
-    PyObject *_UFuncInputCastingError;
-    PyObject *_UFuncNoLoopError;
-    PyObject *_UFuncOutputCastingError;
-    PyObject *math_floor_func;
-    PyObject *math_ceil_func;
-    PyObject *math_trunc_func;
-    PyObject *math_gcd_func;
-    PyObject *os_PathLike;
-    PyObject *os_fspath;
-
-    /*
-     * Used in the __array__ internals to avoid building a tuple inline
-     */
-    PyObject *kwnames_is_copy;
-
-    /*
-     * Used in __imatmul__ to avoid building tuples inline
-     */
-    PyObject *axes_1d_obj_kwargs;
-    PyObject *axes_2d_obj_kwargs;
-
-    /*
-     * Used for CPU feature detection and dispatch
-     */
-    PyObject *cpu_dispatch_registry;
-
-    /*
-     * references to ArrayMethod implementations that are cached
-     * to avoid repeatedly creating them
-     */
-    PyObject *VoidToGenericMethod;
-    PyObject *GenericToVoidMethod;
-    PyObject *ObjectToGenericMethod;
-    PyObject *GenericToObjectMethod;
-} npy_static_pydata_struct;
-
-
-typedef struct npy_static_cdata_struct {
-    /*
-     * stores sys.flags.optimize as a long, which is used in the add_docstring
-     * implementation
-     */
-    long optimize;
-
-    /*
-     * LUT used by unpack_bits
-     */
-    union {
-        npy_uint8  bytes[8];
-        npy_uint64 uint64;
-    } unpack_lookup_big[256];
-
-    /*
-     * A look-up table to recover integer type numbers from type characters.
-     *
-     * See the _MAX_LETTER and LETTER_TO_NUM macros in arraytypes.c.src.
-     *
-     * The smallest type number is ?, the largest is bounded by 'z'.
-     */
-    npy_int16 _letter_to_num['z' + 1 - '?'];
-} npy_static_cdata_struct;
-
 /*
  * A struct storing thread-unsafe global state for the _multiarray_umath
  * module. We should refactor so the global state is thread-safe,
@@ -214,9 +73,6 @@
 } npy_thread_unsafe_state_struct;
 
 
-NPY_VISIBILITY_HIDDEN extern npy_interned_str_struct npy_interned_str;
-NPY_VISIBILITY_HIDDEN extern npy_static_pydata_struct npy_static_pydata;
-NPY_VISIBILITY_HIDDEN extern npy_static_cdata_struct npy_static_cdata;
 NPY_VISIBILITY_HIDDEN extern npy_thread_unsafe_state_struct npy_thread_unsafe_state;
 
 
diff --git a/numpy/_core/src/multiarray/npy_static_data.c b/numpy/_core/src/multiarray/npy_static_data.c
new file mode 100644
index 0000000..be25c05
--- /dev/null
+++ b/numpy/_core/src/multiarray/npy_static_data.c
@@ -0,0 +1,269 @@
+/* numpy static data structs and initialization */
+#define NPY_NO_DEPRECATED_API NPY_API_VERSION
+#define _UMATHMODULE
+#define _MULTIARRAYMODULE
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <structmember.h>
+
+#include "numpy/ndarraytypes.h"
+#include "numpy/npy_common.h"
+#include "numpy/arrayobject.h"
+#include "npy_import.h"
+#include "npy_static_data.h"
+
+// static variables are zero-filled by default, no need to explicitly do so
+NPY_VISIBILITY_HIDDEN npy_interned_str_struct npy_interned_str;
+NPY_VISIBILITY_HIDDEN npy_static_pydata_struct npy_static_pydata;
+NPY_VISIBILITY_HIDDEN npy_static_cdata_struct npy_static_cdata;
+
+NPY_NO_EXPORT int
+intern_strings(void)
+{
+    npy_interned_str.current_allocator = PyUnicode_InternFromString("current_allocator");
+    if (npy_interned_str.current_allocator == NULL) {
+        return -1;
+    }
+    npy_interned_str.array = PyUnicode_InternFromString("__array__");
+    if (npy_interned_str.array == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_function = PyUnicode_InternFromString("__array_function__");
+    if (npy_interned_str.array_function == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_struct = PyUnicode_InternFromString("__array_struct__");
+    if (npy_interned_str.array_struct == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_priority = PyUnicode_InternFromString("__array_priority__");
+    if (npy_interned_str.array_priority == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_interface = PyUnicode_InternFromString("__array_interface__");
+    if (npy_interned_str.array_interface == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_ufunc = PyUnicode_InternFromString("__array_ufunc__");
+    if (npy_interned_str.array_ufunc == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_wrap = PyUnicode_InternFromString("__array_wrap__");
+    if (npy_interned_str.array_wrap == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_finalize = PyUnicode_InternFromString("__array_finalize__");
+    if (npy_interned_str.array_finalize == NULL) {
+        return -1;
+    }
+    npy_interned_str.implementation = PyUnicode_InternFromString("_implementation");
+    if (npy_interned_str.implementation == NULL) {
+        return -1;
+    }
+    npy_interned_str.axis1 = PyUnicode_InternFromString("axis1");
+    if (npy_interned_str.axis1 == NULL) {
+        return -1;
+    }
+    npy_interned_str.axis2 = PyUnicode_InternFromString("axis2");
+    if (npy_interned_str.axis2 == NULL) {
+        return -1;
+    }
+    npy_interned_str.like = PyUnicode_InternFromString("like");
+    if (npy_interned_str.like == NULL) {
+        return -1;
+    }
+    npy_interned_str.numpy = PyUnicode_InternFromString("numpy");
+    if (npy_interned_str.numpy == NULL) {
+        return -1;
+    }
+    npy_interned_str.where = PyUnicode_InternFromString("where");
+    if (npy_interned_str.where == NULL) {
+        return -1;
+    }
+    npy_interned_str.convert = PyUnicode_InternFromString("convert");
+    if (npy_interned_str.convert == NULL) {
+        return -1;
+    }
+    npy_interned_str.preserve = PyUnicode_InternFromString("preserve");
+    if (npy_interned_str.preserve == NULL) {
+        return -1;
+    }
+    npy_interned_str.convert_if_no_array = PyUnicode_InternFromString("convert_if_no_array");
+    if (npy_interned_str.convert_if_no_array == NULL) {
+        return -1;
+    }
+    npy_interned_str.cpu = PyUnicode_InternFromString("cpu");
+    if (npy_interned_str.cpu == NULL) {
+        return -1;
+    }
+    npy_interned_str.dtype = PyUnicode_InternFromString("dtype");
+    if (npy_interned_str.dtype == NULL) {
+        return -1;
+    }
+    npy_interned_str.array_err_msg_substr = PyUnicode_InternFromString(
+            "__array__() got an unexpected keyword argument 'copy'");
+    if (npy_interned_str.array_err_msg_substr == NULL) {
+        return -1;
+    }
+    npy_interned_str.out = PyUnicode_InternFromString("out");
+    if (npy_interned_str.out == NULL) {
+        return -1;
+    }
+    npy_interned_str.__dlpack__ = PyUnicode_InternFromString("__dlpack__");
+    if (npy_interned_str.__dlpack__ == NULL) {
+        return -1;
+    }
+    npy_interned_str.pyvals_name = PyUnicode_InternFromString("UFUNC_PYVALS_NAME");
+    if (npy_interned_str.pyvals_name == NULL) {
+        return -1;
+    }
+    return 0;
+}
+
+#define IMPORT_GLOBAL(base_path, name, object)  \
+    assert(object == NULL);                     \
+    npy_cache_import(base_path, name, &object); \
+    if (object == NULL) {                       \
+        return -1;                              \
+    }
+
+
+/*
+ * Initializes global constants.
+ *
+ * All global constants should live inside the npy_static_pydata
+ * struct.
+ *
+ * Not all entries in the struct are initialized here, some are
+ * initialized later but care must be taken in those cases to initialize
+ * the constant in a thread-safe manner, ensuring it is initialized
+ * exactly once.
+ *
+ * Anything initialized here is initialized during module import which
+ * the python interpreter ensures is done in a single thread.
+ *
+ * Anything imported here should not need the C-layer at all and will be
+ * imported before anything on the C-side is initialized.
+ */
+NPY_NO_EXPORT int
+initialize_static_globals(void)
+{
+    // cached reference to objects defined in python
+
+    IMPORT_GLOBAL("math", "floor",
+                  npy_static_pydata.math_floor_func);
+
+    IMPORT_GLOBAL("math", "ceil",
+                  npy_static_pydata.math_ceil_func);
+
+    IMPORT_GLOBAL("math", "trunc",
+                  npy_static_pydata.math_trunc_func);
+
+    IMPORT_GLOBAL("math", "gcd",
+                  npy_static_pydata.math_gcd_func);
+
+    IMPORT_GLOBAL("numpy.exceptions", "AxisError",
+                  npy_static_pydata.AxisError);
+
+    IMPORT_GLOBAL("numpy.exceptions", "ComplexWarning",
+                  npy_static_pydata.ComplexWarning);
+
+    IMPORT_GLOBAL("numpy.exceptions", "DTypePromotionError",
+                  npy_static_pydata.DTypePromotionError);
+
+    IMPORT_GLOBAL("numpy.exceptions", "TooHardError",
+                  npy_static_pydata.TooHardError);
+
+    IMPORT_GLOBAL("numpy.exceptions", "VisibleDeprecationWarning",
+                  npy_static_pydata.VisibleDeprecationWarning);
+
+    IMPORT_GLOBAL("numpy._globals", "_CopyMode",
+                  npy_static_pydata._CopyMode);
+
+    IMPORT_GLOBAL("numpy._globals", "_NoValue",
+                  npy_static_pydata._NoValue);
+
+    IMPORT_GLOBAL("numpy._core._exceptions", "_ArrayMemoryError",
+                  npy_static_pydata._ArrayMemoryError);
+
+    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncBinaryResolutionError",
+                  npy_static_pydata._UFuncBinaryResolutionError);
+
+    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncInputCastingError",
+                  npy_static_pydata._UFuncInputCastingError);
+
+    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncNoLoopError",
+                  npy_static_pydata._UFuncNoLoopError);
+
+    IMPORT_GLOBAL("numpy._core._exceptions", "_UFuncOutputCastingError",
+                  npy_static_pydata._UFuncOutputCastingError);
+
+    IMPORT_GLOBAL("os", "fspath",
+                  npy_static_pydata.os_fspath);
+
+    IMPORT_GLOBAL("os", "PathLike",
+                  npy_static_pydata.os_PathLike);
+
+    // default_truediv_type_tupS
+    PyArray_Descr *tmp = PyArray_DescrFromType(NPY_DOUBLE);
+    if (tmp == NULL) {
+        return -1;
+    }
+
+    npy_static_pydata.default_truediv_type_tup =
+            PyTuple_Pack(3, tmp, tmp, tmp);
+    if (npy_static_pydata.default_truediv_type_tup == NULL) {
+        Py_DECREF(tmp);
+        return -1;
+    }
+    Py_DECREF(tmp);
+
+    PyObject *flags = PySys_GetObject("flags");  /* borrowed object */
+    if (flags == NULL) {
+        PyErr_SetString(PyExc_AttributeError, "cannot get sys.flags");
+        return -1;
+    }
+    PyObject *level = PyObject_GetAttrString(flags, "optimize");
+    if (level == NULL) {
+        return -1;
+    }
+    npy_static_cdata.optimize = PyLong_AsLong(level);
+    Py_DECREF(level);
+
+    /*
+     * see unpack_bits for how this table is used.
+     *
+     * LUT for bigendian bitorder, littleendian is handled via
+     * byteswapping in the loop.
+     *
+     * 256 8 byte blocks representing 8 bits expanded to 1 or 0 bytes
+     */
+    npy_intp j;
+    for (j=0; j < 256; j++) {
+        npy_intp k;
+        for (k=0; k < 8; k++) {
+            npy_uint8 v = (j & (1 << k)) == (1 << k);
+            npy_static_cdata.unpack_lookup_big[j].bytes[7 - k] = v;
+        }
+    }
+
+    npy_static_pydata.kwnames_is_copy = Py_BuildValue("(s)", "copy");
+    if (npy_static_pydata.kwnames_is_copy == NULL) {
+        return -1;
+    }
+
+    npy_static_pydata.one_obj = PyLong_FromLong((long) 1);
+    if (npy_static_pydata.one_obj == NULL) {
+        return -1;
+    }
+
+    npy_static_pydata.zero_obj = PyLong_FromLong((long) 0);
+    if (npy_static_pydata.zero_obj == NULL) {
+        return -1;
+    }
+
+    return 0;
+}
+
+  
diff --git a/numpy/_core/src/multiarray/npy_static_data.h b/numpy/_core/src/multiarray/npy_static_data.h
new file mode 100644
index 0000000..311f6bc
--- /dev/null
+++ b/numpy/_core/src/multiarray/npy_static_data.h
@@ -0,0 +1,157 @@
+#ifndef NUMPY_CORE_SRC_MULTIARRAY_STATIC_DATA_H_
+#define NUMPY_CORE_SRC_MULTIARRAY_STATIC_DATA_H_
+
+NPY_NO_EXPORT int
+initialize_static_globals(void);
+
+NPY_NO_EXPORT int
+intern_strings(void);
+
+typedef struct npy_interned_str_struct {
+    PyObject *current_allocator;
+    PyObject *array;
+    PyObject *array_function;
+    PyObject *array_struct;
+    PyObject *array_priority;
+    PyObject *array_interface;
+    PyObject *array_wrap;
+    PyObject *array_finalize;
+    PyObject *array_ufunc;
+    PyObject *implementation;
+    PyObject *axis1;
+    PyObject *axis2;
+    PyObject *like;
+    PyObject *numpy;
+    PyObject *where;
+    PyObject *convert;
+    PyObject *preserve;
+    PyObject *convert_if_no_array;
+    PyObject *cpu;
+    PyObject *dtype;
+    PyObject *array_err_msg_substr;
+    PyObject *out;
+    PyObject *errmode_strings[6];
+    PyObject *__dlpack__;
+    PyObject *pyvals_name;
+} npy_interned_str_struct;
+
+/*
+ * A struct that stores static global data used throughout
+ * _multiarray_umath, mostly to cache results that would be
+ * prohibitively expensive to compute at runtime in a tight loop.
+ *
+ * All items in this struct should be initialized during module
+ * initialization and thereafter should be immutable. Mutating items in
+ * this struct after module initialization is likely not thread-safe.
+ */
+
+typedef struct npy_static_pydata_struct {
+    /*
+     * Used in ufunc_type_resolution.c to avoid reconstructing a tuple
+     * storing the default true division return types.
+     */
+    PyObject *default_truediv_type_tup;
+
+    /*
+     * Used to set up the default extobj context variable
+     */
+    PyObject *default_extobj_capsule;
+
+    /*
+     * The global ContextVar to store the extobject. It is exposed to Python
+     * as `_extobj_contextvar`.
+     */
+    PyObject *npy_extobj_contextvar;
+
+    /*
+     * A reference to ndarray's implementations for __array_*__ special methods
+     */
+    PyObject *ndarray_array_ufunc;
+    PyObject *ndarray_array_finalize;
+    PyObject *ndarray_array_function;
+
+    /*
+     * References to the '1' and '0' PyLong objects
+     */
+    PyObject *one_obj;
+    PyObject *zero_obj;
+
+    /*
+     * References to items obtained via an import at module initialization
+     */
+    PyObject *AxisError;
+    PyObject *ComplexWarning;
+    PyObject *DTypePromotionError;
+    PyObject *TooHardError;
+    PyObject *VisibleDeprecationWarning;
+    PyObject *_CopyMode;
+    PyObject *_NoValue;
+    PyObject *_ArrayMemoryError;
+    PyObject *_UFuncBinaryResolutionError;
+    PyObject *_UFuncInputCastingError;
+    PyObject *_UFuncNoLoopError;
+    PyObject *_UFuncOutputCastingError;
+    PyObject *math_floor_func;
+    PyObject *math_ceil_func;
+    PyObject *math_trunc_func;
+    PyObject *math_gcd_func;
+    PyObject *os_PathLike;
+    PyObject *os_fspath;
+
+    /*
+     * Used in the __array__ internals to avoid building a tuple inline
+     */
+    PyObject *kwnames_is_copy;
+
+    /*
+     * Used in __imatmul__ to avoid building tuples inline
+     */
+    PyObject *axes_1d_obj_kwargs;
+    PyObject *axes_2d_obj_kwargs;
+
+    /*
+     * Used for CPU feature detection and dispatch
+     */
+    PyObject *cpu_dispatch_registry;
+
+    /*
+     * references to ArrayMethod implementations that are cached
+     * to avoid repeatedly creating them
+     */
+    PyObject *VoidToGenericMethod;
+    PyObject *GenericToVoidMethod;
+    PyObject *ObjectToGenericMethod;
+    PyObject *GenericToObjectMethod;
+} npy_static_pydata_struct;
+
+
+typedef struct npy_static_cdata_struct {
+    /*
+     * stores sys.flags.optimize as a long, which is used in the add_docstring
+     * implementation
+     */
+    long optimize;
+
+    /*
+     * LUT used by unpack_bits
+     */
+    union {
+        npy_uint8  bytes[8];
+        npy_uint64 uint64;
+    } unpack_lookup_big[256];
+
+    /*
+     * A look-up table to recover integer type numbers from type characters.
+     *
+     * See the _MAX_LETTER and LETTER_TO_NUM macros in arraytypes.c.src.
+     *
+     * The smallest type number is ?, the largest is bounded by 'z'.
+     */
+    npy_int16 _letter_to_num['z' + 1 - '?'];
+} npy_static_cdata_struct;
+
+NPY_VISIBILITY_HIDDEN extern npy_interned_str_struct npy_interned_str;
+NPY_VISIBILITY_HIDDEN extern npy_static_pydata_struct npy_static_pydata;
+NPY_VISIBILITY_HIDDEN extern npy_static_cdata_struct npy_static_cdata;
+
+#endif  // NUMPY_CORE_SRC_MULTIARRAY_STATIC_DATA_H_
diff --git a/numpy/_core/src/multiarray/shape.c b/numpy/_core/src/multiarray/shape.c
index 72bea87..c33272d 100644
--- a/numpy/_core/src/multiarray/shape.c
+++ b/numpy/_core/src/multiarray/shape.c
@@ -19,7 +19,7 @@
 
 #include "shape.h"
 
-#include "multiarraymodule.h" /* for interned strings */
+#include "npy_static_data.h" /* for interned strings */
 #include "templ_common.h" /* for npy_mul_sizes_with_overflow */
 #include "common.h" /* for convert_shape_to_string */
 #include "alloc.h"
diff --git a/numpy/_core/src/umath/_scaled_float_dtype.c b/numpy/_core/src/umath/_scaled_float_dtype.c
index 99d0c64..fbdbbb8 100644
--- a/numpy/_core/src/umath/_scaled_float_dtype.c
+++ b/numpy/_core/src/umath/_scaled_float_dtype.c
@@ -25,6 +25,7 @@
 #include "dtypemeta.h"
 #include "dispatching.h"
 #include "gil_utils.h"
+#include "multiarraymodule.h"
 
 typedef struct {
     PyArray_Descr base;
diff --git a/numpy/_core/src/umath/extobj.c b/numpy/_core/src/umath/extobj.c
index 0405b14..f9194ac 100644
--- a/numpy/_core/src/umath/extobj.c
+++ b/numpy/_core/src/umath/extobj.c
@@ -14,7 +14,6 @@
 #include "extobj.h"
 #include "numpy/ufuncobject.h"
 
-#include "ufunc_object.h"  /* for npy_um_str_pyvals_name */
 #include "common.h"
 
 
diff --git a/numpy/_core/src/umath/funcs.inc.src b/numpy/_core/src/umath/funcs.inc.src
index 131a678..3825bd8 100644
--- a/numpy/_core/src/umath/funcs.inc.src
+++ b/numpy/_core/src/umath/funcs.inc.src
@@ -9,6 +9,7 @@
 #define NPY_NO_DEPRECATED_API NPY_API_VERSION
 
 #include "npy_import.h"
+#include "npy_static_data.h"
 #include "multiarraymodule.h"
 
 /*
diff --git a/numpy/_core/src/umath/override.c b/numpy/_core/src/umath/override.c
index ad9c9a4..b7147a2 100644
--- a/numpy/_core/src/umath/override.c
+++ b/numpy/_core/src/umath/override.c
@@ -4,6 +4,7 @@
 #include "numpy/ndarraytypes.h"
 #include "numpy/ufuncobject.h"
 #include "npy_import.h"
+#include "npy_static_data.h"
 #include "multiarraymodule.h"
 #include "npy_pycompat.h"
 #include "override.h"
diff --git a/numpy/_core/src/umath/ufunc_object.c b/numpy/_core/src/umath/ufunc_object.c
index aedb548..48b4f59 100644
--- a/numpy/_core/src/umath/ufunc_object.c
+++ b/numpy/_core/src/umath/ufunc_object.c
@@ -62,6 +62,8 @@
 #include "legacy_array_method.h"
 #include "abstractdtypes.h"
 #include "mapping.h"
+#include "npy_static_data.h"
+#include "multiarraymodule.h"
 
 /* TODO: Only for `NpyIter_GetTransferFlags` until it is public */
 #define NPY_ITERATOR_IMPLEMENTATION_CODE
diff --git a/numpy/_core/src/umath/ufunc_object.h b/numpy/_core/src/umath/ufunc_object.h
index 645023f..f8e5223 100644
--- a/numpy/_core/src/umath/ufunc_object.h
+++ b/numpy/_core/src/umath/ufunc_object.h
@@ -10,9 +10,4 @@
 NPY_NO_EXPORT PyObject *
 PyUFunc_GetDefaultIdentity(PyUFuncObject *ufunc, npy_bool *reorderable);
 
-/* strings from umathmodule.c that are interned on umath import */
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_array_ufunc;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_array_wrap;
-NPY_VISIBILITY_HIDDEN extern PyObject *npy_um_str_pyvals_name;
-
 #endif
diff --git a/numpy/_core/src/umath/umathmodule.c b/numpy/_core/src/umath/umathmodule.c
index f83e33e..5402b17 100644
--- a/numpy/_core/src/umath/umathmodule.c
+++ b/numpy/_core/src/umath/umathmodule.c
@@ -209,29 +209,6 @@
  *****************************************************************************
  */
 
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_array_ufunc = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_array_wrap = NULL;
-NPY_VISIBILITY_HIDDEN PyObject *npy_um_str_pyvals_name = NULL;
-
-/* intern some strings used in ufuncs, returns 0 on success */
-static int
-intern_strings(void)
-{
-    npy_um_str_array_ufunc = PyUnicode_InternFromString("__array_ufunc__");
-    if (npy_um_str_array_ufunc == NULL) {
-        return -1;
-    }
-    npy_um_str_array_wrap = PyUnicode_InternFromString("__array_wrap__");
-    if (npy_um_str_array_wrap == NULL) {
-        return -1;
-    }
-    npy_um_str_pyvals_name = PyUnicode_InternFromString(UFUNC_PYVALS_NAME);
-    if (npy_um_str_pyvals_name == NULL) {
-        return -1;
-    }
-    return 0;
-}
-
 /* Setup the umath part of the module */
 
 int initumath(PyObject *m)
@@ -297,12 +274,6 @@
     PyDict_SetItemString(d, "conj", s);
     PyDict_SetItemString(d, "mod", s2);
 
-    if (intern_strings() < 0) {
-        PyErr_SetString(PyExc_RuntimeError,
-           "cannot intern strings while initializing _multiarray_umath.");
-        return -1;
-    }
-
     /*
      * Set up promoters for logical functions
      * TODO: This should probably be done at a better place, or even in the