Мы не далеко от первого расширения. Давайте сделаем пример 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.