融合型 (テンプレート)

融合型 (fused type) を使うと、一つの型定義で複数の型を参照できます。 そのため、静的な型の Cython アルゴリズムを書いて、複数の型の値を操作で きます。融合型は C++ におけるテンプレートや Java, C# におけるジェネリクス (generics) のようなもので、「 汎用プログラミング (generic programming)」を実現します。

Note

このサポートは実験的なものなので、バグがあるかもしれません!

クイックスタート

以下のようなコードを書いたとします:

cimport cython

ctypedef fused char_or_float:
    cython.char
    cython.float


cpdef char_or_float plus_one(char_or_float var):
    return var + 1


def show_me():
    cdef:
        cython.char a = 127
        cython.float b = 127
    print 'char', plus_one(a)
    print 'float', plus_one(b)

実行すると:

>>> show_me()
char -128
float 128.0

plus_one(a) は、融合型 char_or_floatchar に「特殊化 (specialize) 」し、 plus_one(b)char_or_floatfloat に特殊化しています。

融合型の宣言

融合型は、以下のように宣言します:

cimport cython

ctypedef fused my_fused_type:
    cython.int
    cython.double

この宣言は、 my_fused_type という新たな型を宣言します。 my_fused_typeint でも double でもあります 。 また、上のような書き方の代わりに、下のようにも書けます:

my_fused_type = cython.fused_type(cython.int, cython.float)

融合型を構成する型の指定には型の名前しか指定できませんが、型は typedef を含む任意の (融合型でない) 型を使えます。従って、下のよう な書き方が可能です:

ctypedef double my_double
my_fused_type = cython.fused_type(cython.int, my_double)

融合型を使う

融合型は関数やメソッドのパラメタの型宣言に使えます:

cdef cfunc(my_fused_type arg):
    return arg + 1

同じ融合型を引数の中で複数回使う場合、それぞれの融合型を特殊化すると、 必ず同じ型になります:

cdef cfunc(my_fused_type arg1, my_fused_type arg2):
    return cython.typeof(arg1) == cython.typeof(arg2)

上のケースでは、両方のパラメタの型は、(前の例に従えば) int または double です。しかし、2つとも同じ融合型 my_fused_type なので、 arg1arg2 は共に同じ型に特殊化します。 従って、この関数は、有意な引数の組み合わせ全てに対して常に True を返し てしまいます。一方、複数の融合型を混ぜた引数の宣言は可能です:

def func(A x, B y):
    ...

上の例では、 AB は別の融合型です。この場合は、 AB に含まれている全ての型の組み合わせについて特殊化したコードパスを 生成します。

融合型とアレイ

通常、数値型は、型の昇格 (promotion) に頼れるので、数値型だけの特殊化 は決して便利とはいえません。しかし、アレイやポインタ、型付きメモリビュー の場合は違います。実際、下のような書き方ができます:

def myfunc(A[:, :] x):
    ...

# and

cdef otherfunc(A *x):
    ...

特殊化の選択

どのような特殊化を行うか (特定、あるいは融合型でない引数に特殊化した引 数の関数のインスタンスを生成するか) は、二つの方法で選べます。一つはイ ンデクシング、もう一つはその関数を呼び出すことです。

インデクシングによる特殊化の選択

以下のように関数をインデクシングすると、特殊化型を選べます:

cfunc[cython.p_double](p1, p2)

# From Cython space
func[float, double](myfloat, mydouble)

# From Python space
func[cython.float, cython.double](myfloat, mydouble)

基本型を融合型に使っている場合、特殊化のインデクシングには基本型を使い ます:

cdef myfunc(A *x):
    ...

# 特殊化のインデクスには、 int* ではなく int を使う
myfunc[int](myint)

呼び出しによる特殊化の選択

融合型の関数は、引数を指定して呼び出すことで、関数の特殊化型が自動的に ディスパッチされます:

cfunc(p1, p2)
func(myfloat, mydouble)

cdefcpdef といった関数を Cython から呼び出す場合には、コン パイル時に特殊化型を判定します。 def 型の場合、引数は実行時に型チェッ クされ、どの特殊型が必要の判定はベストエフォートアプローチで行われます。 そのため、適切な特殊型がないために、実行時に TypeError を引き起こ す可能性があります。 cpdef 関数も、 関数の型が不明の場合 (外部の関 数で、 cimport されていないものの場合) には、 def 型の関数と同じよ うに扱います。

自動ディスパッチの規則は以下の通りで、先に挙げたものの方が優先されます:

  • 厳密にマッチするものを探す
  • 対応する数値型のうち、最もサイズの大きなもの (最大の float 型、最大 の complex 型、最大の int 型) を選ぶ

組み込み融合型

Cython では、利便性のためにいくつか融合型を組込で提供しています:

cython.integral # short, int, long
cython.floating # float, double
cython.numeric  # short, int, long, float, double, float complex, double complex

融合型関数のキャスト

融合型の cdefcpdef 関数をキャストしたり C の関数ポインタに 代入するには、以下のようにします:

cdef myfunc(cython.floating, cython.integral):
    ...

# assign directly
cdef object (*funcp)(float, int)
funcp = myfunc
funcp(f, i)

# alternatively, cast it
(<object (*)(float, int)> myfunc)(f, i)

# This is also valid
funcp = myfunc[float, int]
funcp(f, i)

特殊化型を型チェックする

融合型パラメタがどう特殊化されているかによって、判定処理を行えます。 判定が偽のときのコードパスは、無意味なコードがコンパイルされるのを避け るために除外されます。ある融合型が、別の非融合型と同じかどうか (目的の 型に特殊化されているかどうか) の判定には、 is, is not, ==, != を使えます。また、ある融合型の特殊化型が、別の (融合型として 定義した) 型のセットの一部であるかどうかには、 innot in を 使えます:

ctypedef fused bunch_of_types:
    ...

ctypedef fused string_t:
    cython.p_char
    bytes
    unicode

cdef cython.integral myfunc(cython.integral i, bunch_of_types s):
    cdef int *int_pointer
    cdef long *long_pointer

    # 特殊化型ごとに、以下のブランチのどれか一つだけがコンパイルされます!
    if cython.integral is int:
        int_pointer = &i
    else:
        long_pointer = &i

    if bunch_of_types in string_t:
        print "s is a string!"

__signatures__

最後に、 defcpdef で定義した関数由来の関数オブジェクトには、 __signature__ というアトリビュートがあります。このアトリビュートの 中身は辞書で、シグネチャの文字列を、特殊化した実際の関数に対応付けてい ます。この辞書は、インスペクションの際に役に立つはずです。また、シグネ チャ文字列は、融合関数のインデクスとしても使えます:

specialized_function = fused_function["MyExtensionClass, int, float"]

とはいえ、通常は、以下のような書き方でインデクシングするのが望ましいで しょう:

specialized_function = fused_function[MyExtensionClass, int, float]

ただし、 後者では、Python からこの関数を呼び出すと int 型および float 型に対応する、最も大きな数値型による特殊化を選択してしまいま す。 intfloat は型識別子ではなく、組み込み型を表しているか らです。 cython.intcython.float を指定すれば、この問題は解決します。

Python からメモリビューインデクシングを行う場合には、型の代わりに文字 列を使わねばなりません:

ctypedef fused my_fused_type:
    int[:, ::1]
    float[:, ::1]

def func(my_fused_type array):
    ...

my_fused_type['int[:, ::1]'](myarray)

cython.numeric[:, :] のような表現を使う場合にも、同じことがいえま す。