In CPython, everything (integers, strings, dictionaries, functions, etc) is an “object”, and represented by a C struct named PyObject.
Common to all objects is the storage its type (and the data needed for that type) and a reference count used for garbage collection.

We are interested in integers — type int within Python and PyLongObject within CPython.

Because there is some overhead in creating and destroying objects, CPython maintains a cache for the set of small integers (values -5, …, 256), since these are commonly used.

(At this point, you can probably guess that our module will modify these cached objects).

pycore_global_objects.h defines the range of integers that will be cached,

#define _PY_NSMALLPOSINTS           257
#define _PY_NSMALLNEGINTS           5

In the same file, a few lines later, we see the array that will hold the cached integers. It is an array of type PyLongObject and named small_ints and is within a struct called _Py_global_objects:

struct _Py_global_objects {
    struct {
        /* Small integers are preallocated in this array so that they
         * can be shared.
         * The integers that are preallocated are those in the range
         * -_PY_NSMALLNEGINTS (inclusive) to _PY_NSMALLPOSINTS (exclusive).
        PyLongObject small_ints[_PY_NSMALLNEGINTS + _PY_NSMALLPOSINTS];
    } singletons;

When and where is this array initialized? In pycore_runtime_init.h, we see a macro (itself generated by a Python script) that initializes the elements in the small_ints[] array,

        .small_ints = { 

It is slightly convoluted to see how this
macro (_Py_global_objects_INIT) gets used,
but pystate.c
stores it as static const _PyRuntimeState initial = _PyRuntimeState_INIT;
and this initial variable is used when when the interpreter is initialized.

Read More