こんにちは(@t_kun_kamakiri)(‘◇’)ゞ
この記事では、Pythonで関数やクラスのモジュール化について解説します。
前回の記事で「クラスの基本定義」について解説しましたのでまだ見ていない方は是非どうぞ(^^)/
この記事はこんな人のために書きました。
- Pythonで関数やクラスのモジュール化をしたい
- プログラムの機能ごとにファイルを分けたい
こちらの本がPython初心者が挫折することなく勉強できる本です。
(本記事のようのPython使用環境と異なりますが、とてもわかりやすいので全く問題ありません)
ファイルのダウンロード
今回解説する内容に関しては既にコードを作成していますのでそちらをお使いください。
ダウンロードができたら適当なところで解凍をしておきましょう。
mainファイルに関数を書く
では、さっそく本題に入っていきますが・・・・
いきなり関数のモジュール化をやってもイメージがつかないと思いますので、ひとつひとつ順を追って理解していきましょう。
まずはtest001でべた書きでメインファイルにコードを書いてみましょう。
test001
ここではまだモジュール化をしておらず、メインのファイルに関数を直接ベタ書きする場合を示しています。
main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
import math def get_trialgle(base, height): """ 三角形の面積を計算 base: 底辺[m] height: 高さ[m] """ return base * height /2 def get_circle(radius): """ 円の面積を計算 Area : 円の面積[m] radius: 半径[m] """ Area = radius * radius *math.pi return Area if __name__ == '__main__': print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{get_trialgle(10, 2)}') print(f'円の面積は{get_circle(10)}') |
では、こちらを実行してみましょう。
実行はコマンドプロンプトかVisual Studio CodeにのPower shellで
1 |
python main.py |
と打てば実行されます。
【実行結果】
1 2 3 |
__name__ は__main__となっている。 三角形の面積は10.0 円の面積は314.1592653589793 |
「if __name__ == ‘__main__’:」で何をしているのかわかりにくいかと思ったので、「print(f’__name__ は{__name__}となっている。’)」と出力して__name__がどのような値を取っているのかを確認してみました。
「__name__ は__main__となっている。」となっていると出力されているので、__name__が__main__となっていることがわかりますね。
これは__name__が実行されたモジュール名が値として格納されるので、実行されたファイル(今回はmain.pyですが名前は何でも良い)なら__name__=__main__となっているわけです。
これがメインファイルにベタ書きでコードを書いた場合です。
モジュール化:関数を別ファイルに分ける
関数を別ファイルに分けてmain.pyから呼び出す方法はいくつかあります。
ここでは代表的なものを3つ紹介します。
- from get_math import get_trialgle, get_circle
- from get_math import *
- import get_math
- import get_math as g
test002:
今度は関数を別のファイルに分けてみましょう。
ファイル構成は以下のようになっています。
1 2 3 |
C:. │ get_math.py │ main.py |
get_math.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import math def get_trialgle(base, height): """ 三角形の面積を計算 base: 底辺[m] height: 高さ[m] """ return base * height /2 def get_circle(radius): """ 円の面積を計算 Area : 円の面積[m] radius: 半径[m] """ Area = radius * radius *math.pi return Area print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') |
呼び出される方のファイル内で__name__ がどうなっているのかを確認するために「print(f’__name__ は{__name__}となっている。’)」と出力してみましょう。
main.py
1 2 3 4 5 6 7 |
from get_math import get_trialgle, get_circle if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{get_trialgle(10, 2)}') print(f'円の面積は{get_circle(10)}') |
メインファイルがかなりすっきりしているなっていますね。
実行はコマンドプロンプトかVisual Studio CodeにのPower shellで
1 |
python main.py |
と打てば実行されます。
【実行結果】
1 2 3 4 5 6 |
----------get_math---------- __name__ はget_mathとなっている。 ----------__main__---------- __name__ は__main__となっている。 三角形の面積は10.0 円の面積は314.1592653589793 |
- get_math.pyでは「__name__」はそれ自身のファイル名になっています。
- main.pyの中でget_math.pyの中の関数を使うためには、
「from get_math import get_trialgle, get_circle」という記述をファイルの先頭に書いています。
1from モジュール import 関数/クラス
今回は関数を使うためimportの後は関数名を書きましたが、後ほどやるようにクラスを設定することもできます。 - 別ファイルの関数を使うときはget_trialgle(10, 2)として使うだけ。
このようにしてmainのファイルはやりたいことをただ記述するだけでよく、複雑なアルゴリズムは別ファイルに分けておくと便利ですよね。
ユーザーにとって別ファイルの複雑なアルゴリズムがメインのファイルに書かれているとちょっと見る気を失せてしまいますし(‘_’)
test003: from get_math import *
test002では「from get_math import get_trialgle, get_circle」と書き「get_math の中の関数get_trialgleとget_circleを呼び出しますよ」と宣言しましたが、
1 |
from get_math import * |
という書き方をして「get_math」ファイルの中のすべての関数/クラスを呼び出しますという書き方もできます。
main.py
1 2 3 4 5 6 7 |
from get_math import * if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{get_trialgle(10, 2)}') print(f'円の面積は{get_circle(10)}') |
※get_math.pyの変更はありません。
出力結果はtest002と同じですがtest003のmain.pyの「from get_math import *」のような書き方はお勧めをしません。
- どのモジュールに属する関数/クラスを使用しているのかが不明瞭
- 「import *」ですべての関数/クラスをインポートしているのでメモリ消費を無駄にしている
以上の理由から「import *」 という書き方はしないようにしましょう。
test004 : import get_math
モジュールだけインポートする書き方もできます。
main.py
1 2 3 4 5 6 7 |
import get_math if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{get_math.get_trialgle(10, 2)}') print(f'円の面積は{get_math.get_circle(10)}') |
冒頭に「import get_math」とだけ書き「 get_math」の中の関数を使う場合は、「get_math.get_trialgle(10, 2)」と書いてドット(.)でつなげて関数を呼び出します。
※get_math.pyの変更はありません。
出力結果はtest002と同じですがtest003よりは良い書き方かと思います。
test005 : import get_math as g
1 |
import get_math as g |
これは「get_math を gと略します」という宣言になります。
なので、関数を呼び出すときに、
get_math.get_trialgle(10, 2)
↓
g.get_trialgle(10, 2)
かなりコードがすっきりしますね。
main.py
1 2 3 4 5 6 7 |
import get_math as g if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{g.get_trialgle(10, 2)}') print(f'円の面積は{g.get_circle(10)}') |
※get_math.pyの変更はありません。
以上で別ファイルから関数を呼び出す3つの記述方法を紹介しました。
- 〇 from get_math import get_trialgle, get_circle
- × from get_math import *
- △ import get_math
- 〇 import get_math as g
お勧めとしては①と④の方法かと思います。
③もよく使うので問題ない書き方です。
パッケージ化:複数のファイルをフォルダに分ける
複数のモジュール(ファイル)がある場合、コードの規模が多くなって名前が被ってしまうこともおります。
そこで複数のモジュール(ファイル)をひとまとめにした仕組みがパッケージです。
モジュールとかパッケージとかちょっと曖昧な表現をされるとわかりにくいのですよね。
絵で描くと以下のようなイメージです。
※今回はパッケージはひとつだけにします。
test006
ファイル構成は以下のようにします。
1 2 3 4 5 |
C:. │ main.py │ └─myget_math get_math.py |
- メインファイル(main.py)
- パッケージ(フォルダ):myget_math
モジュール(ファイル):get_math.py
main.py
1 2 3 4 5 6 7 |
from myget_math import get_math if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{get_math.get_trialgle(10, 2)}') print(f'円の面積は{get_math.get_circle(10)}') |
メインファイルはパッケージからモジュールを呼び出すために冒頭で「from myget_math import get_math」と書いています。
1 |
from パッケージimport モジュール |
このような書き方ですね。
モジュール(ファイル)の中の関数を使うときはドット(.)でつなげていくだけなので、「get_math.get_trialgle(10, 2)」とすれば関数を呼び出すことができます。
※get_math.pyの変更はありません。
※結果もtest002から変更がありません。
test007
次はtest006とファイル構成が同じでmain.pyからパッケージ内のモジュールの呼び出し方を変えてみましょう。
1 |
import パッケージ.モジュール |
パッケージからモジュールをインポートする宣言をしています。
main.py
1 2 3 4 5 6 7 |
import myget_math.get_math if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{myget_math.get_math.get_trialgle(10, 2)}') print(f'円の面積は{myget_math.get_math.get_circle(10)}') |
モジュール(ファイル)内の関数を呼び出すのにimportで書いた記述をドットでつなげていくので「myget_math.get_math.get_trialgle(10, 2)」と書きます。
ちょっと長い記述になってしまいましたがこちらも正常に動作する正しい記述です。
※get_math.pyの変更はありません。
※結果もtest002から変更がありません。
test008
test007で関数を呼び出すのに「myget_math.get_math.get_trialgle(10, 2)」と長い記述を書きましたが、以下のような略語を設定すれば記述がすっきりします。
1 |
import パッケージ.モジュール as 略 |
これで「myget_math.get_math」が「g」と名前付けされています。
main.py
1 2 3 4 5 6 7 |
import myget_math.get_math as g if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{g.get_trialgle(10, 2)}') print(f'円の面積は{g.get_circle(10)}') |
これでモジュール(ファイル)内の関数を呼び出すのに「g.get_trialgle(10, 2)」と書けばよく、とてもすっきりした記述になりますね。
※get_math.pyの変更はありません。
※結果もtest002から変更がありません。
test009
次にファイル構成を以下のようにします。
1 2 3 4 5 6 |
C:. │ main.py │ └─myget_math │ get_math.py │ __init__.py |
test007~test008とどこが変わったかというと、パッケージ(フォルダ)「myget_math」内に「__init__.py」というファイルを用意しました。
main.py
1 2 3 4 5 6 7 |
import myget_math if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{myget_math.get_math.get_trialgle(10, 2)}') print(f'円の面積は{myget_math.get_math.get_circle(10)}') |
main.pyでこのように書き、「import myget_math」というパッケージ(フォルダ)をインポートしただけでは以下のようなエラーが出てしまします。
1 |
AttributeError: module 'myget_math' has no attribute 'get_math' |
これは「myget_math」がモジュールとして認識されてしまいその中に「get_math」という属性が存在しないというエラーです。
今回「myget_math」はモジュールではなくパッケージとして認識する必要があるので、「import myget_math」としたときにパッケージ内で最初に実行されるファイルというのを用意しておきます。
それが「__init__.py」です。
「__init__.py」を「myget_math」内に用意します。
1 2 3 |
from myget_math import get_math print('====実行された====') |
main.pyが実行されるとmain.pyの冒頭「import myget_math」がインポートされ、「myget_math」内の「__init__.py」が実行され、「from myget_math import get_math」によりモジュールがインポートされるという仕組みです。
※get_math.pyの変更はありません。
少し難しいですが実行してみればどういう動作をしているのかがわかるでしょう。
実行はコマンドプロンプトかVisual Studio CodeにのPower shellで
1 |
python main.py |
と打てば実行されます。
【実行結果】
1 2 3 4 5 6 7 |
----------myget_math.get_math---------- __name__ はmyget_math.get_mathとなっている。 ====実行された==== ----------__main__---------- __name__ は__main__となっている。 三角形の面積は10.0 円の面積は314.1592653589793 |
「__init__.py」に「print(‘====実行された====’)」という記述を書いたので「from myget_math import get_math」のモジュールがインポートされたあとに「’====実行された====」という出力がされています。
test010
今度はパッケージ内に複数のファイルを用意してみます。
1 2 3 4 5 6 7 |
C:. │ main.py │ └─myget_math get_math.py get_math_multi.py __init__.py |
パッケージ「myget_math」内に、もう一つモジュール「get_math_multi.py」を追加しました。
main.py
1 2 3 4 5 6 7 8 9 |
import myget_math if __name__ == '__main__': print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{myget_math.get_math.get_trialgle(10, 2)}') print(f'円の面積は{myget_math.get_math.get_circle(10)}') print(f'四角形の面積は{myget_math.get_math_multi.get_trialgle2times(10, 2)}') |
__.init__.py
1 2 3 4 |
from . import get_math from . import get_math_multi print('====実行された====') |
「from . import get_math」という書き方をしています。
「from .」のドット(.)は現在のフォルダに対して「get_math.py」をインポートするという意味です。
現在のフォルダは「__init__.py」がいるフォルダのことなので「myget_math」の事ですね。
「myget_math/get_math.py」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import math def get_trialgle(base, height): """ 三角形の面積を計算 base: 底辺[m] height: 高さ[m] """ return base * height /2 def get_circle(radius): """ 円の面積を計算 Area : 円の面積[m] radius: 半径[m] """ Area = radius * radius *math.pi return Area print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') |
「myget_math/get_math_multi.py」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import math def get_trialgle2times(base, height): """ 四角形の面積は三角形の面積の2倍 base: 底辺[m] height: 高さ[m] """ triangle = base * height /2 return 2 *triangle print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') |
実行はコマンドプロンプトかVisual Studio CodeにのPower shellで
1 |
python main.py |
と打てば実行されます。
【実行結果】
1 2 3 4 5 6 7 8 9 10 |
----------myget_math.get_math---------- __name__ はmyget_math.get_mathとなっている。 ----------myget_math.get_math_multi---------- __name__ はmyget_math.get_math_multiとなっている。 ====実行された==== ----------__main__---------- __name__ は__main__となっている。 三角形の面積は10.0 円の面積は314.1592653589793 四角形の面積は20.0 |
このようにパッケージ(フォルダ)何に複数のモジュール(ファイル)を用意することができます。
test011
次はtest010と同じファイル構成で「__init__.py」を以下の書き換えます。
__.init__.py
1 2 3 |
from . import get_math, import get_math_multi print('====実行された====') |
test010では2行になっていたのを1行にまとめました。
※その他のファイルは変数なし
※結果はtest010と同じ
クラスにまとめる
今までは「myget_math」というパッケージ内に「get_math.py」と「get_math_multi.py」というモジュールを用意しましたが、大きなくくりでいうと2つのモジュールは何かを計算している機能を持ったプログラムというくくりにできるため、これらをクラスという形でまとめておきます。
test012
今度はパッケージ内に複数のファイルを用意してみます。
1 2 3 4 5 6 |
C:. │ main.py │ └─myget_math │ get_math_multi.py │ __init__.py |
test011にはパッケージ内に2つのモジュールがありましたが今回は「get_math_multi.py」だけにしています。
その代わりにクラスを用いて複数の関数を持つようなコードに書き換えました。
「myget_math/get_math_multi.py」
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
import math class CalcMath: def __init__(self, base, height, radius): self.base = base self.height = height self.radius = radius def get_trialgle(self): """ 三角形の面積を計算 base: 底辺[m] height: 高さ[m] """ return self.base * self.height /2 def get_trialgle2times(self): """ 四角形の面積は三角形の面積の2倍 base: 底辺[m] height: 高さ[m] """ triangle = self.base * self.height /2 return 2 *triangle def get_circle(self): """ 円の面積を計算 Area : 円の面積[m] radius: 半径[m] """ Area = self.radius * self.radius *math.pi return Area print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') |
__.init__.py
1 2 3 |
from .get_math_multi import CalcMath print('====実行された====') |
「from .get_math_multi import CalcMath」という書き方をしています。
「from .get_math_multi」のドット(.)は現在のフォルダ内のget_math_multiモジュールを読み込むという意味です。
さらに、「import CalcMath」でCalcMathクラスをインポートしています。
main,py
1 2 3 4 5 6 7 8 9 10 11 |
import myget_math if __name__ == '__main__': # クラスのインスタンス化 calc = myget_math.get_math_multi.CalcMath(10, 100, 1000) print('-'*10 + f'{__name__}' + '-'*10) print(f'__name__ は{__name__}となっている。') print(f'三角形の面積は{calc.get_trialgle()}') print(f'四角形の面積は{calc.get_trialgle2times()}') print(f'円の面積は{calc.get_circle()}') |
「calc = myget_math.get_math_multi.CalcMath(10, 100, 1000)」でクラスのインスタンス化を行っています。
引数に「CalcMath(10, 100, 1000)」と入れてクラスが実行されたときに実行される初期化関数「def __init__(self, base, height, radius):」を「myget_math/get_math_multi.py」内で使っています。
実行はコマンドプロンプトかVisual Studio CodeにのPower shellで
1 |
python main.py |
と打てば実行されます。
【実行結果】
1 2 3 4 5 6 7 |
----------myget_math.get_math_multi---------- __name__ はmyget_math.get_math_multiとなっている。 ====実行された==== ----------__main__---------- __name__ は__main__となっている。 三角形の面積は500.0 四角形の面積は1000.0 |
このようにパッケージ(フォルダ)何に複数のモジュール(ファイル)を用意することができます。
test013
最後はtest012でmain.pyの冒頭に「import myget_math」と書いていたために、クラスのインスタンス化で「calc = myget_math.get_math_multi.CalcMath(10, 100, 1000)」とドット(.)でつなぐのが長くなっていたの以下のように略語で使うように書き換えます。
1 |
import パッケージ as 略語 |
こうすると記述がすっきりしますね。
main.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import myget_math as mg math_dic = { 'small': [10, 100 ,1000], 'medium' : [100, 1000 , 10000], 'big' : [1000, 10000, 10000] } if __name__ == '__main__': for key, val in math_dic.items(): print(f'======{key}==========') calc = mg.CalcMath(val[0], val[1], val[2]) #インスタンス化(初期状態) print(f'三角形の面積は{calc.get_trialgle()}') print(f'四角形の面積は{calc.get_trialgle2times()}') print(f'円の面積は{calc.get_circle()}')さ |
コードがすっきりしましたね。
さらに今回のmain.pyは複数条件で計算が行えるように以下のように辞書型で2つの条件を書いています。
1 2 3 4 5 |
math_dic = { 'small': [10, 100 ,1000], 'medium' : [100, 1000 , 10000], 'big' : [1000, 10000, 10000] } |
これを以下のfor文で繰り返し処理を行うようにしました。
1 |
for key, val in math_dic.items(): |
実行はコマンドプロンプトかVisual Studio CodeにのPower shellで
1 |
python main.py |
と打てば実行されます。
【実行結果】
1 2 3 4 5 6 7 8 9 10 11 12 |
======small========== 三角形の面積は500.0 四角形の面積は1000.0 円の面積は3141592.653589793 ======medium========== 三角形の面積は50000.0 四角形の面積は100000.0 円の面積は314159265.3589793 ======big========== 三角形の面積は5000000.0 四角形の面積は10000000.0 円の面積は314159265.3589793 |
複数条件での結果が出力されましたね(^^♪
まとめ
Pythonのパッケージ化/モジュール化についての説をしました。
機能ごとにファイルを分けて整理すると利便性が増しますね。
↓以下がPython初学者のためのお勧めの参考書です。
本記事の内容に関して詳しくは↓こちらの参考書に記載があります。