FortranでもSOLID原則を意識してみる
オブジェクト指向をするならSOLID原則が大事なんですね。今頃になって知ることができたので、今後はtype,abstract
を効果的に使うことができそうです。なお、この記事では、計算速度のことは考えないことにします。
以下のコードは https://github.com/sakamoti/fortran_oop_from_python から抜粋したので、動かしたい人はcloneしてみてください。
抽象基底クラスの定義
インスタンスが作れない、型の情報だけをまず定義します。インスタンスを生成する際には、これを継承してメソッドを上書きする必要があります。
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種類を定義しました。
また、クラスを生成するファクトリをインターフェースで作成することで、インスタンス生成時の引数の数でクラス実体が決まるようにしています。
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
を活用しているので、クラスの配列も簡単に生成できます。
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から呼び出し)
<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クラスの相互運用もできることが分かったので満足。
ただ、かなり面倒。。