Мы не далеко от первого расширения. Давайте сделаем пример xxmodule.c, настоящее расширение Python вместо встроенного модуля.

Мы оставим двоичный файл интерпретатора Python нетронутым и создадим расширение как разделяемую библиотеку. Оператор импорта Python загрузит библиотеку с помощью dl_open и сделает ее фактическим модулем Python, который вы можете использовать в интерпретаторе.

# Copy the xxmodule.c file to a new location
# Assuming that you're in the debug folder
cd ../.. 
cp cpython/Modules/xxmodule.c .
# Also create some shortcuts to the compile binary
ln -s cpython/debug/python python
ln -s cpython/debug/python-config python-config

Все еще помните, как создать общую библиотеку? Да, с помощью команды --fPIC --shared. Но на этот раз мы также должны указать компилятору, где найти заголовочные файлы, используя флаги -ILIBRARY_PATH. Итак, вот полная команда, которую вы можете скопировать.

gcc -shared -fPIC -Icpython/Include -Icpython/debug xxmodule.c -o xx$(bash python-config --extension-suffix)

На практике вы также можете использовать -Wall для отображения всех предупреждений (иногда они действительно полезны!) и -O3 для оптимизации кода.

Сгенерированный файл общей библиотеки называется xx.cpython-312d-x86_64-linux-gnu.so в соответствии с моими настройками. Имя имеет значение, так как Python будет искать точное имя при загрузке модулей. Слово 312d в названии означает, что эта библиотека не будет работать с версиями Python, отличными от 3.12. (попробуйте import xx из другой версии Python, и вы получите ошибку ModuleNotFoundError)

На практике вы можете написать простой скрипт distutils, который собирает модуль для локальной установленной версии Python наpip install.

Также обратите внимание, что имя модуля — xx, и для него требуется символ PyInit_xx, определенный в библиотеке.

./python
>>> import xx
>>> xx.bug([1,2,3])
1

Выглядит отлично.

И вы можете попробовать переименовать модуль во что-то вроде xx2.

cp xx.cpython-312d-x86_64-linux-gnu.so xx2.cpython-312d-x86_64-linux-gnu.so
./python
>>> import xx2
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: dynamic module does not define module export function (PyInit_xx2)

ХОРОШО. Итак, в файле xxmodule.c давайте сделаем копию функции PyInit_xx и назовем ее PyInit_xx2. Не забудьте также скопировать строку с PyMODINIT_FUNC . Теперь скомпилируйте его, и импорт должен работать.

gcc -shared -fPIC -Icpython/Include -Icpython/debug xxmodule.c -o xx2$(bash python-config --extension-suffix)
./python
>>> import xx2
>>> xx2.__doc__

Хороший.

Вы также можете использовать LD_DEBUG=files ./python, чтобы проверить, когда и какой библиотечный файл был загружен компоновщиком времени выполнения.

Теперь у вас есть основа для понимания расширений Python. Это то же самое, что и встроенные модули, но они распределяются и загружаются по-разному. Их довольно легко написать, и это одна из основных причин популярности Python, несмотря на то, что код на чистом Python медленный.

В следующей главе мы вернемся к теме pybind11 и посмотрим, как система шаблонов типов C++ помогает при написании расширений Python.

›› Глава 4: Передача аргументов