読者です 読者をやめる 読者になる 読者になる

座敷牢日誌

都落ちした元SEがソフトウェアやネット関連のことを書いています

Python C APIでイテレータを実装する (前編)

Python

PythonのC APIイテレータを実装してみました。 Pythonであればyield を使ったり、__iter__, __next__ を使って実装するものですね。 語義的にジェネレータと区別できてないところがあるかもしれません。

指定した個数だけ素数を返すという機能を実装する例を上げます。 リストやタプルではなく、next() で取得できる、range 関数みたいな形で。

import math2
for n in math2.Primes(0, 5):
    print(n)

math2.Primes(begin, length) begin に最初の値を、 length にかえしてほしい素数の個数を指定して、実行。 上記コードを実行すると、2, 3, 5, 7 が返されるという想定です。

比較用にPythonで書く場合とC言語を組み合わせた場合のコードを示します。

これを試した、調べた目的は、Pythonの一部をCで実装するコストと効果と 実践的に確認したかったからです。同じような考えの人の参考になれば幸いですね。

あと、C言語のコードがかなり出てきますが、私、C言語はそれほど詳しくありません。 参考にしてはいけない、まずいコードがあったらご了承ください。 詳しい方のツッコミは歓迎します。

Pythonでの実装

Pythonでの実装例です。isprime 関数に素数かどうかを判定させ、 Primes クラスがイテレータとして指定した個数の素数を返します。

素数判定する関数 isprime

まず、 isprime 関数。こいつに素数判定させます。

def isprime(n):
    if n < 2:
        return False
    elif n == 2:
        return True

    if n % 2 == 0:
        return False

    i = 3
    while i <= n / i:
        if n % i == 0:
            return False
        i += 2

    return True

元ネタはWikipediaから拝借しました。

素数判定 - Wikipedia

イテレータ用クラス Primes

そして Primes クラス。挙動としては前述したように range 関数みたいな感じですね。 yield を使った関数でも同じことができると思いますが、 それに相当するものがC APIでできるかよく分からなかったので、ここではクラスにしました。

class Primes(object):

    def __init__(self, begin, length):
        self.__begin = begin
        self.__length = length

    def __iter__(self):
        self.__returned = 0
        self.__current = self.__begin - 1
        return self

    def __next__(self):
        while self.__returned < self.__length:
            self.__current += 1
            if isprime(self.__current):
                self.__returned += 1
                return self.__current
        raise StopIteration

これらを math2 モジュールとして保存します。

動作確認用 テストコード

動作確認用に用意したテストコード。同じコードをC APIを用いたモジュールでも 通るようにするところまでが目標です。

import unittest
import math2

class Math2TestCase(unittest.TestCase):

    def test_isprime(self):
        for n in range(32):
            if n in [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]:
                self.assertTrue(math2.isprime(n))
            else:
                self.assertFalse(math2.isprime(n))

    def test_primes(self):
        self.assertListEqual([2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31],
                list(math2.Primes(0, 11)))

C言語での実装

math2.h での宣言。あまりC言語に明るくないので、ちょっと恥ずかしいんですけど。 非staticなモノはすべてモジュール名をプリフィクスに、関数名はアンダースコアで区切って、 クラス的なものとそのメソッドがあればさらにアンダースコアで区切る形にしています。

math2_Bool math2_isPrime(int n);
void math2_Primes_initialize(math2_Primes *pThis, int begin, int length);
math2_Bool math2_Primes_next(math2_Primes *pThis, int *value);

素数判定の関数 math2_isPrime

math2_isPrime 関数は渡された値が素数かを返します。 定義はこんな感じで。

math2_Bool math2_isPrime(int n)
{
    int i;

    if(n < 2)
        return math2_False;
    else if(n == 2)
        return math2_True;

    if(n % 2 == 0)
        return math2_False;

    for(i = 3; i <= n / i; i += 2)
        if(n % i == 0)
            return math2_False;

    return math2_True;
}

このあたりはPython版とあまり変わらないと思います。

構造体 math2_Primes

math2_Primes_initializemath2_Primes_next で使っている math2_Primes は 構造体です。 イテレータで実装予定なので、配列で返すのはよくないかなと思い、状態を保持する目的で 用意しました。このあたり、もっといいやり方があれば教えて欲しいくらい。

typedef struct
{
    int begin;
    int length;
    int current;
    int returned;
} math2_Primes;

math2_Primes 初期化関数 math2_Primes_initialize

math2_Primes_initialize 関数は構造体 Primes の初期化用で、 定義は以下の通り。第1引数に 構造体 Primes のポインタをメモリ確保したうえで 渡す必要があります。このあたりのメモリの面倒はどこで見るのがいいのか, Cに詳しくないので、ちょっと自信なし。

void math2_Primes_initialize(math2_Primes *pThis, int begin, int length)
{
    pThis->begin = begin;
    pThis->length = length;
    pThis->current = begin - 1;
    pThis->returned = 0;
}

値を順次返す関数 math2_Primes_next

math2_Primes_next 関数がPythonで作ったクラスでいう __next__ に相当します。 返す値がなくなったら False (-1) を返します。

math2_Bool math2_Primes_next(math2_Primes *pThis, int *value)
{
    if(pThis->returned == pThis->length)
        return math2_False;
    while(1)
    {
        pThis->current += 1;
        if(math2_isPrime(pThis->current))
        {
            *value = pThis->current;
            pThis->returned += 1;
            return math2_True;
        }
    }
}

C言語での使用例

C言語での使用例を示します。

#include <stdio.h>
#include "math2.h"

int main()
{
    int n;
    math2_Primes *primes;

    math2_Primes_initialize(primes, 0, 10);
    while(math2_Primes_next(primes, &n))
        printf("%d\n", n);
    return 0;
}

実行すると、0から10個の素数を出力して終わります。

後編、Pythonモジュールとして実装する

ここまでがC言語での実装で、これをPythonモジュールとして利用可能にする方法を 後編に紹介します。

ソースコード

作成したソースをbitbucketに置いたので、必要なら参考にしてみてください。 archlinux, Python 3.4, cmake 3.1.1, GNU Make 4.1, GCC 4.9.2 で動作確認しています。 確認したわけではないですが、(経験上) cygwinでも動くとは思います。(経験上) WindowsのVS環境で動かす自信はありません。

https://kuchida1981@bitbucket.org/kuchida1981/python-capi-examples.git

広告を非表示にする