Python C拡張でFortranのClassを操る

# FortranでもSOLID原則を意識してみる
オブジェクト指向をするならSOLID原則が大事なんですね。今頃になって知ることができたので、今後は`type,abstract` を効果的に使うことができそうです。なお、この記事では、計算速度のことは考えないことにします。

以下のコードは [https://github.com/sakamoti/fortran_oop_from_python](https://github.com/sakamoti/fortran_oop_from_python) から抜粋したので、動かしたい人はcloneしてみてください。

## 抽象基底クラスの定義
インスタンスが作れない、型の情報だけをまず定義します。インスタンスを生成する際には、これを継承してメソッドを上書きする必要があります。
```fortran
    type,abstract,public :: IShape
        !! Interface representing a generic shape.
        character(len=:),private,allocatable :: mytype
    contains
        !! abstractmethods
        procedure(IcalculateArea),pass,deferred,public :: calculatearea
        procedure(Ishowinfo),pass,deferred,public :: showinfo
    end type

    abstract Interface
        pure elemental function IcalculateArea(self) result(area)
            import IShape, c_double
            class(IShape),intent(in) :: self
            real(kind=c_double) :: area
            !! Caluculate the area of the shape.
            !!
            !! Returns
            !! -------
            !! c_double 
            !!     Area of the shape.
        end function
        subroutine Ishowinfo(self)
            import IShape
            class(IShape) :: self
            !! Show information.
        end subroutine
    end Interface
```

## クラス実体の定義
`Circle_t`と`Rectangle_t`の2種類を定義しました。
また、クラスを生成するファクトリをインターフェースで作成することで、インスタンス生成時の引数の数でクラス実体が決まるようにしています。
```fortran
    type,extends(IShape),public :: Circle_t
        real(c_double),private :: radius
    contains
        procedure :: calculatearea => calculatearea_circle
        procedure :: showinfo => showinfo_circle
    end type

    type,extends(IShape),public :: Rectangle_t
        real(c_double),private :: width, height
    contains
        procedure :: calculatearea => calculatearea_rectangle
        procedure :: showinfo => showinfo_rectangle
    end type

    !! Factory definithion ------
    public ShapeFactory
    interface ShapeFactory
        procedure :: circle__init__, rectangle__init__
    end interface
```

## Fortranのメイン関数
`class(IShape),allocatable`としているので、`select type`を書かなくてよくなります。また、`pure elemental`を活用しているので、クラスの配列も簡単に生成できます。
```fortran
program test_fort_main
    use, intrinsic :: iso_fortran_env , only : dp => real64
    use m_shape, only : IShape, Circle_t, Rectangle_t, ShapeFactory, &
                        test_circle_area, test_rectangle_area
    class(IShape),allocatable :: circle, rectangle
    class(IShape),allocatable :: circle_array(:), rectangle_array(:)
    integer :: i

    !! test
    call test_circle_area()
    call test_rectangle_area()

    !! scalar
    circle = ShapeFactory(radius=3d0)
    rectangle = ShapeFactory(width=2d0, height=3d0)
    !! show information
    print '("#--- scalar version ---")'
    call circle%showinfo()
    call rectangle%showinfo()

    !! generate class array like python's zip method
    allocate(circle_array, source = ShapeFactory(radius=[1d0,2d0,3d0]))
    allocate(rectangle_array, source = ShapeFactory(width=[1d0,2d0,3d0], height=[4d0,5d0,6d0]))
    !! show informations
    print '("#--- array version ---")'
    do i = 1, size(circle_array)
        print '("  -- array(",I0,") ---")',  i
        call circle_array(i)%showinfo()
        call rectangle_array(i)%showinfo()
    end do
end program
```

# こういうFortranライブラリをPythonから使いたい
近年 `iso_c_binding`の機能が拡充されてきていますが、そもそもC言語でクラスの概念が無いので、Fortranで作成したクラスをCで簡単に利用できるわけではありません。一方、Python C拡張ではクラスを実現することができます。その特徴として下記のような項目があげられます。
- クラスは構造体として作成されるが、それらはすべて`PyObject`になる
- 整数、浮動小数点、リスト、辞書、関数、など、あらゆるデータ型が`PyObject`とよばれる
- 構造体成分にアロケータ、デアロケータなどの関数を指定する
- メソッドを表現する特別な構造体を、クラスの構造体の成分として持つ

どのように説明すれば良いかわかりませんが、「クラスが無い言語で頑張ってクラスを実現している」という印象です。であれば、Fortranのクラスを上記のような要素に分解して`PyObject`を作ることができれば、めでたくPythonからFortranクラスを利用することができるわけです。

## 実現方針
- クラスのポインタをCの`void*`に変換
- クラスのメソッドを呼び出す場合、`void*`からFortranのポインタへ変換した後にそのメソッドをFortranで呼び出す（fortranのiso_c_bindingを活用）

ただ、`type,abstruct`を使って新しくPython側で子クラスを定義することは諦めます。（そもそもできるかどうかわかりません。できない気がする。）

## ソースコード
Python C拡張はかなり面倒なので、GitHubのコードとPythonのドキュメントを眺めてください。（説明を書く余力がございませんでした。）

## 実行結果の例（Pythonから呼び出し)
```bash
<module 'custom' from '/home/test/fortran_oop_from_python/custom.so'>
<custom.FCircle object at 0x7890c61fe6d0>
 【 Hello from Fortran showinfo method 】
    type   : Circle_t
    radius :    1.0000000000000000     
    area   :    3.1415926535897931     
 【 Hello from Fortran showinfo method 】
    type   : Circle_t
    radius :    2.0000000000000000     
    area   :    12.566370614359172     
 【 Hello from Fortran showinfo method 】
    type   : Circle_t
    radius :    3.0000000000000000     
    area   :    28.274333882308138     

# ---- get area as PyObject ---
<class 'float'> 3.141592653589793
<class 'float'> 12.566370614359172
<class 'float'> 28.274333882308138
```

# あとがき（感想）
PythonのC拡張を書くと、かなり細かいところまで制御できるようです。
念願の、Fortranクラスの相互運用もできることが分かったので満足。
ただ、かなり面倒。。