Try   HackMD

IDEでPythonのC/C++拡張モジュールを開発する

キーワード: Python, extension, C/C++, IDE, Visual Studio, CLion, VSCode, デバッガ

Pythonを用いてライブラリやアプリケーションを開発していると、性能向上や複雑な処理を実現するために、C/C++で開発されたネイティブコードと連携させたいことがあります。

あるいは、C/C++で開発しているライブラリやツールで、Pythonのインターフェースを提供したいことがあります。

PythonとCのBindingの開発では、複数の良質なドキュメントがあるため、最初の一歩を容易に踏みだせるようになっています。

C/C++拡張モジュールの開発の文書の例

Microsoft Windowsに依存した拡張作成であれば、Microsoftのドキュメントに従って開発するといいでしょう。ここではPythonらしく、マルチプラットフォームで利用できるライブラリを作成するときに活用できる開発環境のノウハウを共有したいとおもいます。

C/C拡張モジュールの開発のためのAPIやC/Cの流儀などについては、本稿ではふれません。

Python開発環境

さて、Pythonの開発では、どんなエディターや開発環境を利用していますか?

2017年から、Pythonソフトウエア財団とJetbrainsが共同でおこなうデベロッパー調査が4年にわたり継続的に行なわれており、その結果を見てみましょう。

2万4000超人の開発者に聞いた、Pythonで使う開発環境第1位は?(2019年調査結果)

順位 プロダクト シェア
1       PyCharm    33%
2       VS Code     24%
3       Vim               9%
4       Sublime Text 6%

PyCharmとVS Codeで調査された開発者たちの約半数になります。

2020年の調査結果では、開発環境のオペレーテングシステムの調査結果では、Linuxが一位で、より経験年数の多い開発者ほどmacOSかLinuxを選択する傾向があるようです。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

わたしもLinuxをプライマリーの開発環境にしています。この記事では、Linux, Windows, macOSで共通して利用できる開発環境を対象にしていきます。PyCharmとVS Codeは、この条件にマッチしています。

2020年の調査では、前年にくらべ、VS Codeのシェアが増していますが、PyCharmも引き続き首位を維持しています。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

C/C++拡張モジュール開発の開発環境

C/C++拡張モジュールを開発するときの開発環境はどうでしょうか。

C++開発者調査2020 では、C++開発者がプライマリーに利用しているエディターや開発環境を調査しています。

順位 プロダクト     回答数
1       Visual Studio  351         
2       VS Code         162
3       Vim                 135
4      CLion               99

ここで、これまで出てこなかった CLion がでてきました。CLionは、PyCharmを開発するJetBrains社のプロダクトの一つで、C/C++開発用のマルチプラットフォームのIDEです。PyCharm Professionalの機能をプラグインで追加して利用できるため、Python/Cの双方を開発できます。実は、筆者は、PyCharm ProfessionalとCLionと、(Java用のIntelliJ IDEA Ultimateも)、Linux上で利用しています。

無償で導入できて、PythonとC/C++の開発に利用できて、マルチプラットフォームで動作する VS Code を本稿では利用していきます。残念ながら筆者はVS Codeをほとんど利用していないため、時々、CLionの事例をはさみこみます。

VS Codeの導入と設定

さて、VS Codeを導入して、PythonとC/C++拡張を開発できるように設定していきましょう。

VS Codeのダウンロード

Visual Studio Code(VS Code)のダウンロードサイトから利用している環境にあったパッケージをダウンロードして導入してください。

拡張機能の導入

インストールできたら、拡張機能を導入していきます。まず、"C/C++"を拡張機能の検索ボックスにいれて、"C/C++ Extension Pack"を選択して導入してください。C/C++開発に必要な複数の拡張機能が一気に導入されます。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

続いて、"Python"で検索して、Pythonサポートの拡張機能を導入します。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

PythonとC/C++コンパイラーの導入

開発するわけですから、PythonとC/C++コンパイラーを導入します。

Linux(Ubuntu, Mint, Debian, etc)では

apt install python3-dev python3-venv

Linux(Fedora, CentOS, etc)では、

sudo yum install python3-devel.x86_64

macOSでは、

xcode-select --install

Windowsを主たる開発環境にする場合は、必要なツールをブラウザで手動でダウンロードして、適切に設定してインストールする必要があります。

* Python for Windows download から最新の安定板を導入

Microsoft Build Tools for Visual Studio 2019 から、Visual C++ 14.2 compilerを導入

ここでPythonインストール時に、カスタム導入を選択し、開発ライブラリを導入するように選ぶのがコツです。

VMイメージの利用

macOSやLinuxを主たる開発環境として選択しつつ、Windowsでの動作確認をおこなうには、マイクロソフトの Get a Windows 10 development environment ページからVirtualBox や Parallels 用の開発用のVMイメージを利用することができます。

このVMイメージでは、2021年8月現在、次のような環境が設定された状態になってダウンロードすることができます。

  • Windows 10 Enterprise Evaluation 21H1 (10.0.20348.0)
  • Windows 10 SDK, version 2104 (10.0.20348.0)
  • Visual Studio 2019
  • Visual Studio Code
  • Windows Subsystem for Linux enabled with Ubuntu installed
  • Developer mode enabled

このイメージ上で、 Visual Studio Installer を起動し、 C++ デスクトップ開発と Python 開発を選択して、モジュールを追加することで環境がととのいます。VMイメージのOSのライセンスは30日の期間限定です。

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

本稿の環境では、 "C++ CMake tools for Windows"と、"Python native development tools"、"MSVC v142", "Windows 10 SDK"が必ず含まれるように注意してください。VM環境では、これで環境がととのいます。

プロジェクトを作成する

Pythonプロジェクトの開始

Pythonプロジェクトを作成します。最近の流儀だと、pyproject.tomlファイルとsetup.cfgに記述していくのがモダンです。公開するOSS開発にも適合するように、ディレクトリ構造は次のようにします。

<プロジェクトルート>/
   README.rst
   LICENSE.rst
   pyproject.toml
   setup.py
   setup.cfg
   MANIFEST.in
   Changelog.rst
   SECURITY.rst
   src/
     sample/
        __init__.py
   tests/
   docs/
   .gitignore
   .github/
     workflows/
        tox.yml

最低限のpyproject.tomlはつぎのような内容です。

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

setup.cfgはつぎのようになります。

[metadata]
name = sample
description = sample project
long_description = file: README.rst
long_description_content_type = text/x-rst
keywords = sample
license = LGPL-2.1+
author = Hiroshi Miura
author_email = miurahr@linux.com
url = https://example.com/
classifiers =
     Development Status :: 2 - Pre Alpha
     License :: OSI Approved :: GNU Lesser General Public License v2 or later (
LGPLv2+)

[options]
python_requires = >=3.6
setup_requires =
     setuptools

setup.pyはつぎのようになります。

from setuptools import setup

setup()

C/C++ 拡張を追加するPythonプロジェクト設定の追加

さて、C/C++拡張の設定を追加しましょう。extディレクトリを作成し、拡張モジュールのCソースを追加します。

   src/
     sample/
        __init__.py
        c/
          c_sample.py
        cffi/
          cffi_sample.py
     ext/
          _samplemodule.c
          ffi_build.py

setup.pyに拡張をビルドする設定を追加します。

from setuptools import setup, Extension
from setuptools.command.build_ext import build_ext


setup(
   ext_modules=[
     Extension("sample._sample",
       {"include_dirs": [],
        "library_dirs": [],
        "libraries": [],
        "name": "sample.c._sample",
        "sources": ["src/ext/_samplemodule.c"],
        "define_macros": []
       }
   )],
 )

このディレクトリ構成は、CPython APIの拡張および CFFIによる拡張の両方を開発する構成になります。CによるPython3実行環境では、CPython APIによる拡張を動作させられますが、Pythonを高速実行できるPyPyではCFFIによる拡張が必要です。

setup.pyの例は、CPython APIの拡張について記述されています。

C/C++ と Pythonコードをデバッガ実行する環境づくり

PythonのC/Cの拡張を開発していきますと、Pythonコードのデバッグと、C/Cのデバッグを行き来しながら開発したくなります。

さきほどご紹介した商用製品のC/C++ IDEのCLionの機能改善要望チケットで、C/C++とPythonのクロスデバッグ サポート要望が6年前からあがっています。

Cross-debugging Python and C++ (91イイネ)

IDEで実現するのはとてもむずかしいようです。CLionにはPython拡張が統合されているため、Pythonコードを実行すると自動的にPythonデバッガーが起動してしまい、C/C++のバグをデバッグすることができません。

そのため、手動でデバッグ版のpythonを端末から実行してデバッガーをアタッチ開始するという方法が紹介されています。

C/C++のビルド定義

ここまでの定義では、pythonとしての定義がされていますが、C/Cとしての定義がありませんので、C/C IDEは、C/Cのコードをあつかうことが十分にできません。そこで、C/Cのクロスプラットフォーム クロスツールのビルドツールであるCMake の定義ファイルを作成します。

モダンなIDEでは、ほとんどの場合、CMakeのサポートがありますので、一度作成すれば、他のIDEへ移行することも容易です。VSCodeでも使えます。

まずは、pythonのC/C++拡張をビルドさせる設定です。途中、OSごとにことなる拡張子を判定するためのコードがふくまれています。

CMakeList.txt

cmake_minimum_required(VERSION 3.19)
project(sample C CXX)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
find_package(Python 3.9 COMPONENTS Interpreter Development)

#拡張子を判定
file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/get_ext.py
    "import sysconfig\nprint(sysconfig.get_config_var('EXT_SUFFIX'))\n")
execute_process(
 COMMAND ${Python_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/get_ext.py
 OUTPUT_VARIABLE PY_EXT_EXT
 OUTPUT_STRIP_TRAILING_WHITESPACE)

# 拡張モジュルのファイル名を定義
set(PY_EXT src/sample/c/_sample.${PY_EXT_EXT})
# 拡張モジュールの生成方法の定義
add_custom_target(
 generate_ext
 BYPRODUCTS ${PY_EXT}
 COMMAND ${BUILD_EXT_PYTHON} setup.py build_ext --inplace
 WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
 DEPENDS venv.stamp
 SOURCES ${pyppmd_sources})

これで、C/CのIDEからPythonを起動して、PythonのC/C拡張モジュールを生成させることができるようになりました。generate_extというターゲットとなります。

次は、virtualenvの定義です。python実行時のPython依存関係について、C/C++のビルド定義の一環で実現します。

# create virtualenv
set(VENV_PATH ${CMAKE_BINARY_DIR}/venv)
if (WIN32)
 set(PIP_COMMAND ${VENV_PATH}/Scripts/pip.exe)
else()
 set(PIP_COMMAND ${VENV_PATH}/bin/pip)
endif()
add_custom_target(
       venv.stamp
       BYPRODUCTS venv.stamp
       COMMAND ${Python_EXECUTABLE} -m venv ${VENV_PATH}
       COMMAND ${PIP_COMMAND} install -r ${CMAKE_SOURCE_DIR}/requirements.txt
       COMMAND ${CMAKE_COMMAND} -E touch venv.stamp)
set(VPKG_PATH_A "${VENV_PATH}/lib/python${Python_VERSION_MAJOR}.${Python_VERSION
_MINOR}/site-packages/")
set(VPKG_PATH_B "${VENV_PATH}/Lib/site-packages/")

つづいて、pythonのテストを起動する定義です。C/C++アプリケーションとして、Pythonを起動するため、pytest_runnerという簡単なアプリを定義しています。

# For pytest
file(
 WRITE ${CMAKE_CURRENT_BINARY_DIR}/pytest_runner.cpp
 "
#include <string>
#include <filesystem>
#include <Python.h>
int main(int argc, char **argv) {
   std::string args;
   if ( argc > 1) {
       args.append(\"[\");
       for (int i = 1; i < argc; i++) {
           if (i > 2)
               args.append(\",\");
           args.append(\"\\\"\");
           args.append(argv[i]);
           args.append(\"\\\"\");
       }
       args.append(\"]\");
   }
   std::filesystem::path vsite_path_a = \"${VPKG_PATH_A}\";
   std::filesystem::path vsite_path_b = \"${VPKG_PATH_B}\";
   std::string pycode =
       \"import sys\\n\"
       \"import pytest\\n\"
       \"sys.path.append('src')\\n\"
       \"sys.path.append('\" + vsite_path_a.string() + \"')\\n\"
       \"sys.path.append('\" + vsite_path_b.string() + \"')\\n\"
       \"pytest.main(\" + args + \")\\n\";
   wchar_t * program_name = Py_DecodeLocale(argv[0], NULL);
   Py_SetProgramName(program_name);
   Py_Initialize();
   PyRun_SimpleString(&*pycode.begin());
   Py_Finalize();
   return 0;
}")  

add_executable(pytest_runner ${CMAKE_CURRENT_BINARY_DIR}/pytest_runner.cpp)
​target_include_directories(pytest_runner PRIVATE ${Python_INCLUDE_DIRS})
target_link_libraries(pytest_runner PRIVATE ${Python_LIBRARIES})
add_dependencies(pytest_runner venv.stamp generate_ext)

こうすることで、C/CのIDEが、C/Cのカスタウアプリを実行しているつもりで実行すると、pythonがよみこまれて、pytestが起動するようになります。実行時のディレクトリが、pythonのtestをよみこめるようにIDEで定義してください。

VS Codeでデバッグ

これ以降では、具体的なプロジェクトをもとにして、デバッグ実行していきましょう。前半でご紹介したWindows の評価版の環境で実行します。

サンプルとして、筆者が開発するPython用のテキスト圧縮伸長ライブラリのPyPPMd を利用します。PyPPMdは、PPM (Prediction by partial matching)という圧縮アルゴリズムを提供するPythonライブラリです。ここでは具体的な圧縮アルゴリズムについては説明しません。リンク先のWikipedia記事などを参照してください。

  1. Visual Studio Installerで、PythonとC/C++の環境をととのえます。
  2. VS Code の拡張機能を設定します。
  3. VSCodeでgithubからリポジトリをクローンします。
  4. コマンドパレットから、CMake(cmake) - configure を実行します

すると次のように、MS Visual C++ 2019 と、VIsual Studio同梱のPython 3.7 を検出してくれました。

続いて、コマンドパレットから CMake: Build Targetを選択し、generate_ext UTILITYを選択します。すると、Pythonを起動して、自動的に拡張機能をビルドしてくれます。CMakeの設定のおかげです。

デバッガー起動

ライセンス

この作品は、クリエイティブ・コモンズ表示 4.0 国際(CC-BY 4.0 lnternational)ライセンスで公開されています。

筆者について

オープンストリートマップを普及、整備を促進する非営利の一般社団法人 オープンストリートマップ・ファウンデーション・ジャパンの代表理事をつとめています。オープンデータとフリーソフトウエアがますます拡大して、人々が自由にソフトウエアを開発しサービスを提供して、人々がさまざまなサービスを利用できるようになり、よりよい世の中になることを期待しています。