.. _python-bindings:
A09 - Misturando Python e linguagens compiladas
================================================
.. questions:: Questão
- Como podemos lidar com projetos multilinguagens com CMake?
.. objectives:: Objetivos
- Aprenda a construir ligações Python pybind11.
- Aprenda a construir ligações CFFI Python.
Python é uma linguagem de programação dinâmica flexível.
Como o próprio Python é escrito na linguagem de programação C, é possível
escrever módulos de *extensão* em uma linguagem compilada.
Obtém-se toda a flexibilidade evitando as penalidades de desempenho
inerentes às linguagens interpretadas. Muitos frameworks estão disponíveis
para preencher a lacuna entre linguagens compiladas e
Python. Todos eles contam com alguma forma de geração automática de código:
- `SWIG `_. Possivelmente o framework com a história mais longa.
- `CFFI `_. Funciona com C e Fortran.
- `Cython `_. Funciona com C e pode exigir muito esforço.
Misturando C++ e Python com pybind11
++++++++++++++++++++++++++++++++++++++
Se você está escrevendo C++, você tem ainda mais opções de frameworks de vinculação:
- `Boost.Python
`_.
Adaptado para C++ e conta com metaprogramação de modelo para gerar ligações em tempo de compilação.
- `pybind11 `_. Mesma filosofia do Boost.Python, mas projetado para C++11 e além.
Se você escrever *modern C++*, pybind11 deve ser sua estrutura de escolha:
- É uma biblioteca somente de cabeçalho e, portanto, uma dependência bastante fácil de satisfazer.
- O código de ligação será bastante compacto: você não terá que manter uma base de código excessivamente grande.
- Possui excelente integração com o CMake.
.. exercise:: Exercício 27: Código bancário com C++ e Python
Nosso objetivo é compilar wrappers Python para uma pequena
biblioteca C++ simulando uma conta bancária. A dependência pybind11
será satisfeita no momento da configuração usando ``FetchContent``.
O projeto base está em ``source/code/day-2/27_cxx-pybind11``.
A estrutura da pasta é a seguinte:
.. code-block:: text
27_cxx-pybind11
└── account
├── account.cpp
├── account.hpp
└── test.py
#. Crie um ``CMakeLists.txt`` na raiz do programa, com
requisito e projeto mínimo do CMake.
#. Encontre o Python com |find_package|. Solicite pelo menos a versão
3.6 com a palavra-chave ``REQUIRED`` e o interpretador e
os cabeçalhos de desenvolvimento com a palavra-chave
``COMPONENTS``. Consulte a documentação:
.. code-block:: bash
$ cmake --help-module FindPython | less
#. Habilite o teste e adicione a pasta ``account``.
#. Preencha o ``CMakeLists.txt`` base na pasta ``account``,
seguindo os prompts do ``FIXME``. Queremos baixar o tarball
lançado para a versão 2.6.2 do pybind11.
#. Configure, construa e execute o teste.
Uma solução funcional está na subpasta ``solution``.
.. note::
- A função ``pybind11_add_module`` é um wrapper de conveniência para |add_library| para
gerar módulos de extensão Python. É oferecido por pybind11 e você
pode ler mais sobre isso `aqui
`_.
- A sintaxe especial usada na definição do comando test definirá o local da
extensão Python como uma variável de ambiente.
Misturando C/Fortran e Python com CFFI
+++++++++++++++++++++++++++++++++++++++
`CFFI `_, abreviação de "C Foreign Function Interface", é um módulo Python que
ajuda na criação de interfaces Python para projetos C-interoperáveis.
Usar CFFI pode ser um pouco mais simples do que trabalhar com pybind11.
No entanto, ele permite que você crie interfaces Python para projetos
Fortran de forma mais direta do que com Cython ou SWIG.
Isso requer alguns passos:
#. escrever um arquivo de cabeçalho C definindo a
interface de programação de aplicativos (API) do seu código.
#. invocando CFFI para analisar o arquivo de cabeçalho da API e
produzir o código de wrapper C correspondente.
#. compilando o código wrapper gerado em um módulo Python.
Embora a etapa 1 dependa do código para o qual você deseja fornecer o
código de vinculações do Python, as etapas 2 e 3 podem ser automatizadas
em um sistema de compilação CMake.
.. exercise:: Exercício 28: Código bancário usando CFFI
Nosso objetivo é compilar wrappers Python para uma pequena biblioteca simulando uma conta bancária.
O código de exemplo já tem um arquivo de cabeçalho da API.
Este exercício mostrará como realizar as etapas 2 e 3 acima:
- O script Python ``cffi_builder.py`` analisa o arquivo de cabeçalho da
API e irá gerar o arquivo fonte ``_pyaccount.c`` no
*tempo de compilação*. Conseguimos isso no CMake usando um
comando personalizado, emparelhado com um destino personalizado.
.. code-block:: cmake
add_custom_command(
OUTPUT
${PROJECT_BINARY_DIR}/generated/_pyaccount.c
COMMAND
${Python_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/cffi_builder.py
MAIN_DEPENDENCY
${CMAKE_CURRENT_LIST_DIR}/cffi_builder.py
DEPENDS
${CMAKE_CURRENT_LIST_DIR}/account.h
WORKING_DIRECTORY
${PROJECT_BINARY_DIR}/generated
)
add_custom_target(
pyaccount-builder
ALL
DEPENDS
${PROJECT_BINARY_DIR}/generated/_pyaccount.c
)
Isso garante que o arquivo seja regenerado sempre que o cabeçalho da API for alterado.
- Uma vez que o ``_pyaccount.c`` esteja disponível, nós o construímos
como um módulo Python, usando a função ``Python_add_library``,
fornecida no módulo ``FindPython`` do CMake.
.. code-block:: cmake
Python_add_library(_pyaccount
MODULE
account.f90
${PROJECT_BINARY_DIR}/generated/_pyaccount.c
)
# add dependency between _pyaccount target and pyaccount-builder custom target
add_dependencies(_pyaccount pyaccount-builder)
.. tabs::
.. tab:: Fortran
Um projeto base está em ``source/code/day-2/28_fortran-cffi``.
A estrutura do código é a seguinte:
.. code-block:: text
28_fortran-cffi/
├── account
│ ├── account.f90
│ ├── account.h
│ ├── cffi_builder.py
│ ├── CMakeLists.txt
│ ├── __init__.py
│ └── test.py
└── CMakeLists.txt
Siga os prompts do ``FIXME`` em ``CMakeLists.txt`` para fazer o projeto compilar.
#. Declare um projeto usando C e Fortran.
#. Encontre o Python com |find_package|.
Solicite pelo menos a versão 3.6 com a palavra-chave
``REQUIRED`` e o interpretador e os cabeçalhos de
desenvolvimento com a palavra-chave ``COMPONENTS``.
Consulte a documentação:
.. code-block:: bash
$ cmake --help-module FindPython | less
#. Adicione a pasta ``account`` e ative o teste.
#. Preencha ``CMakeLists.txt`` na pasta ``account``, seguindo os prompts do ``FIXME``.
#. Configure, construa e execute o teste.
Uma solução funcional está na subpasta ``solution``.
.. tab:: C++
Um projeto base está em ``content/code/day-2/28_cxx-cffi``.
A estrutura do código é a seguinte:
.. code-block:: text
28_fortran-cffi/
├── account
│ ├── account.cpp
│ ├── account.hpp
│ ├── c_cpp_interface.cpp
│ ├── account.h
│ ├── cffi_builder.py
│ ├── CMakeLists.txt
│ ├── __init__.py
│ └── test.py
└── CMakeLists.txt
#. Declare o projeto C++.
#. FEncontre o Python com |find_package|.
Solicite pelo menos a versão 3.6 com a palavra-chave
``REQUIRED`` e o interpretador e os cabeçalhos de
desenvolvimento com a palavra-chave ``COMPONENTS``.
Consulte a documentação:
.. code-block:: bash
$ cmake --help-module FindPython | less
#. Adicione a pasta ``account`` e ative o teste.
#. Preencha ``CMakeLists.txt`` na pasta ``account``, seguindo os prompts do ``FIXME``.
#. Configure, construa e execute o teste.
A estrutura do código é a seguinte:
.. keypoints:: Resumo
- O CMake pode simplificar o sistema de compilação para projetos complexos e multilíngues.