コンテンツにスキップ

最終更新:2026-10-12

CPython 拡張開発メモ

この記事では、最近 Apache に提出した feature を背景に、CPython で遭遇した問題を語り、さらに 0 から自分でライブラリを書く試みまで紹介します。コードも星球に置いてあるので、興味のある方はダウンロードして学んでみてください。

0.背景

最近、apache arrow に提出した feature で、C++ インターフェースの変更が他言語のインターフェース変更を引き起こし、一部のテストも合わせて修正が必要になりました。

PyArrow に詳しい人ならまったく馴染みがないわけではないでしょう。今回のインターフェース変更でもこのライブラリを修正する必要があります。同じリポジトリ内にあるため、直さないと CI が通りません。PyArrow の実装は CPython によるもので、これまで特に CPython を学んだことがなかったため、いろいろ問題を出してしまいました。そのうち 2 つの問題が比較的重要なので、ここに記録しておきます。

問題 1:初期化関数の中では他のクラスのデフォルト構築がサポートされない。

例:

 def __init__(self, mode="only_valid", filter=Expression._scalar(True)):
        pass

エラー:

TypeError: descriptor '_scalar' for 'pyarrow._compute.Expression' objects doesn't apply to a 'bool' object

認識されなかったことが分かります。実際には Expression._scalar(True) は合法であり、中の実装を見てみます:

@staticmethod
def _scalar(value):
    cdef:
        Scalar scalar

    if isinstance(value, Scalar):
        scalar = value
    else:
        scalar = lib.scalar(value)

    return Expression.wrap(CMakeScalarExpression(scalar.unwrap()))

中では通常の bool 型をサポートしていることが分かります。私はこれが cpython の制限ではないかと疑い、次のように変更したところ上手くいきました:

def __init__(self, mode="only_valid", filter=None):
    if filter is None:
        filter = Expression._scalar(True)

問題 2:定義の順序

後から作成した _true を使うと、毎回渡されるデフォルト値が空になります。これは理解しやすく、最終的にコンパイルが完了すると 1 つの xxx.cpp ファイルに翻訳されるため、C++ の規則により、前方で読み取られるものは当然空になるのです。

def __init__(self, mode="only_valid", filter=_true):
        pass


cdef CExpression _true = CMakeScalarExpression(
    <shared_ptr[CScalar]> make_shared[CBooleanScalar](True)
)

さて、以上の背景を踏まえて、私自身も 1 つ例を書いてみたくなりました。例えば:C++ でクラスを書いて sort と sum をカプセル化し、Python から呼び出すというものです。

1.CPython 完全な例

  1. .h ファイルを作成
void sort(std::vector<int>& nums) {
    std::sort(nums.begin(), nums.end());
}
int sum(std::vector<int>& nums) {
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    return sum;
}

2) foo.pyx を作成

重要なポイント:上の vector には次が必要です:

from libcpp.vector cimport vector

次に class を定義して、C++ のインターフェースを呼び出します。

cdef class PyFoo:
    cdef Foo* f

    def __cinit__(self):
        self.f = new Foo()

    def __dealloc__(self):
        del self.f

    def sort(self, nums):
        cdef vector[int] c_nums = nums
        self.f.sort(c_nums)

    def sum(self, nums):
        cdef vector[int] c_nums = nums
        return self.f.sum(c_nums)
  1. setup.py ファイルを作成
ext = Extension('Foo', sources=["foo.pyx"], language="c++", include_dirs=[numpy.get_include()])

setup(name="Foo", ext_modules = cythonize([ext]))
  1. 実行
python3 setup.py build_ext --inplace

最後に、テストスクリプトを書いて自作の python インターフェースを使ってみましょう。

import Foo

f = Foo.PyFoo()
nums = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
f.sort(nums)
print("Sorted nums:", nums)
print("Sum of nums:", f.sum(nums))

CPython はいくつかのプロジェクトでかなり使われています。学んでいきましょう〜

実行:

➜  cpython_examples python3 test.py 
Sorted nums: [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
Sum of nums: 44

関連トピック

コメント