2025, Sep 26 03:17

Как найти исходники NumPy: _multiarray_umath, .so и Meson

Разбираем, почему concatenate и inner в NumPy живут в .so: что такое _multiarray_umath, как найти исходники C/C++ через meson introspect: состав сборки.

Заглядывая в ядро NumPy и не находя обычного Python-файла для функций вроде concatenate или inner, многие проходят через один и тот же этап. Причина проста: они обращаются к скомпилированному разделяемому модулю, который поставляется как .so, а не .py. Понимание, что это за файл, откуда он берётся и как отследить его до исходников, заметно упрощает чтение и отладку внутренних механизмов NumPy.

Где начинается путаница

В numpy/_core/multiarray.py вы увидите, что базовые API оформлены декораторами и подключены через импорт, выглядящий как обычный относительный импорт. Но рядом нет файла _multiarray_umath.py. Вместо этого NumPy устанавливает бинарник с именем _multiarray_umath.cpython-313-x86_64-linux-gnu.so в каталоге numpy/_core. Именно этот .so и импортируется конструкцией from . import _multiarray_umath.

Минимальный код, который показывает привязку

Можно убедиться, что Python импортирует бинарный разделяемый модуль, и посмотреть, где он находится. Логика проста: импортируйте модуль и проверьте его метаданные.

import importlib
ua_handle = importlib.import_module('numpy._core._multiarray_umath')
print(type(ua_handle).__name__)
print(getattr(ua_handle, '__file__', 'no-file-attr'))

Это демонстрирует, что импорт приводит к подключению разделяемого модуля (.so) и показывает путь на диске внутри numpy/_core.

Что собой представляет этот .so

Этот .so — разделяемый модуль, собранный примерно из сотни исходников на C и C++, разбросанных по numpy/_core/src, включая подкаталоги вроде multiarray и umath. Часть исходников — это сгенерированные C-файлы, а не написанные вручную. Сборку определяет и управляет ею конфигурация Meson в NumPy.

Как понять, как он собирается

Самый прямой способ разобраться — собрать NumPy из исходников и затем проинспектировать граф сборки с помощью Meson. После настройки сборки можно вывести машиночитаемое описание верхнеуровневых целей:

meson introspect --targets -i build/ > targets.json

Найдите в targets.json цель _multiarray_umath. Вы увидите запись, где указано, что это разделяемый модуль, где он определён, куда устанавливается и какие исходники и сгенерированные файлы в него входят. Показательный фрагмент выглядит так:

{
  "name": "_multiarray_umath.cpython-313-x86_64-linux-gnu",
  "type": "shared module",
  "defined_in": "/home/user/numpy/numpy/_core/meson.build",
  "filename": [
    "/home/user/numpy/build/numpy/_core/_multiarray_umath.cpython-313-x86_64-linux-gnu.so"
  ],
  "target_sources": [
    {
      "language": "c",
      "sources": [
        "/home/user/numpy/numpy/_core/src/multiarray/arrayobject.c",
        "/home/user/numpy/numpy/_core/src/multiarray/multiarraymodule.c",
        "/home/user/numpy/numpy/_core/src/umath/umathmodule.c"
      ],
      "generated_sources": [
        "/home/user/numpy/build/numpy/_core/_multiarray_umath.cpython-313-x86_64-linux-gnu.so.p/arraytypes.c",
        "/home/user/numpy/build/numpy/_core/_multiarray_umath.cpython-313-x86_64-linux-gnu.so.p/einsum.c"
      ]
    },
    {
      "language": "cpp",
      "sources": [
        "/home/user/numpy/numpy/_core/src/umath/clip.cpp",
        "/home/user/numpy/numpy/_core/src/umath/string_ufuncs.cpp"
      ]
    }
  ],
  "dependencies": ["openblas", "python-3.13"],
  "install_filename": [
    "/usr/lib/python3.13/site-packages/numpy/_core/_multiarray_umath.cpython-313-x86_64-linux-gnu.so"
  ]
}

Отсюда очевидны две вещи. Во-первых, модуль объявлен в numpy/_core/meson.build. Во-вторых, помимо написанных вручную файлов на C/C++, он включает сгенерированные C-исходники, перечисленные в generated_sources. Обычно такие файлы получаются из шаблонов .c.src. Интроспекция Meson не раскрывает подробности шага генерации на этом уровне.

Как отследить исходники в дереве

Если хочется полистать нужный код, идите прямо в numpy/_core/src. В этом каталоге лежат реализации на C и C++, которые компилируются в импортируемый вами разделяемый модуль. Среди прочего, вы увидите поддеревья multiarray и umath. Найти в Python-пространстве обёртки вроде vstack легко, но чтобы проследить concatenate или inner до C-уровня, придётся читать эти C/C++-файлы и местами сгенерированный на этапе сборки C-код.

Рабочий путь: интроспекция и чтение определений Meson

Практичный сценарий такой: соберите NumPy из исходников и проинспектируйте сборку Meson, как показано выше. Затем, чтобы точно понять состав модуля _multiarray_umath, откройте правила Meson, которые его объявляют. Описание сборки модуля находится в конфигурации Meson в NumPy: там перечислены исходники и сгенерированные файлы и указано, как они компилируются. Посмотреть можно здесь: numpy/_core/meson.build.

Зачем это знать мейнтейнерам и продвинутым пользователям

Осознание того, что ключевые API NumPy — это тонкие Python-обёртки над большим скомпилированным модулем, объясняет, почему для критичных по производительности операций вы не найдёте чисто-python’ных тел функций. Это также подсказывает, где искать, когда нужно проверить поведение, отладить низкоуровневую проблему, разобраться с флагами CPU или изучить, как сгенерированный код попадает в итоговый бинарник. Вывод интроспекции Meson — надёжная карта от импортируемого .so назад к его исходникам, флагам компилятора и зависимостям на этапе линковки.

Итоги

Если не удаётся найти .py для ключевой функции NumPy, считайте, что за ней стоит разделяемый модуль в numpy/_core. Подтвердите импорт и путь в Python, затем соберите из исходников и воспользуйтесь meson introspect --targets -i build/, чтобы перечислить точные исходники и сгенерированные файлы, входящие в _multiarray_umath. Если сомневаетесь — откройте numpy/_core/meson.build и проследите определения. Такой подход держит вас в рамках того, что действительно компилируется и устанавливается, и помогает без догадок перейти от высокоуровневого API к лежащим под ним реализациям на C и C++.

Статья основана на вопросе на StackOverflow от user90189 и ответе Nick ODell.