A09 - Misturando Python e linguagens compiladas

Questão

  • Como podemos lidar com projetos multilinguagens com CMake?

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.

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:

27_cxx-pybind11
└── account
    ├── account.cpp
    ├── account.hpp
    └── test.py
  1. Crie um CMakeLists.txt na raiz do programa, com requisito e projeto mínimo do CMake.

  2. 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:

    $ cmake --help-module FindPython | less
    
  3. Habilite o teste e adicione a pasta account.

  4. 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.

  5. 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:

  1. escrever um arquivo de cabeçalho C definindo a interface de programação de aplicativos (API) do seu código.

  2. invocando CFFI para analisar o arquivo de cabeçalho da API e produzir o código de wrapper C correspondente.

  3. 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.

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.

    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.

    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)
    

Um projeto base está em source/code/day-2/28_fortran-cffi. A estrutura do código é a seguinte:

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.

  1. Declare um projeto usando C e Fortran.

  2. 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:

    $ cmake --help-module FindPython | less
    
  3. Adicione a pasta account e ative o teste.

  4. Preencha CMakeLists.txt na pasta account, seguindo os prompts do FIXME.

  5. Configure, construa e execute o teste.

Uma solução funcional está na subpasta solution.

Resumo

  • O CMake pode simplificar o sistema de compilação para projetos complexos e multilíngues.