Python基礎8:モジュール/パッケージ
概要
Pythonの基礎としてモジュール及びパッケージを説明します。一般的な用語の説明は下記の通りです。
1.モジュール
モジュールとはPythonコードが記載されたファイルです。Pythonコードをモジュール化することで再利用性・保守性が高まります。
本章での説明用ファイル構成は下記の通りです。
1-1.モジュールの作成
モジュールの作成はpyファイルにPythonコードを記載するだけです。まず説明用に”sayhello.py”を作成しました。このコードには1-4節の内容を含めていないため、実際の適用品は異なるコードとなります。
[In] sayhello.py
import sys
varhello_JP='変数:こんにちは'
def Hello(Language: str):
if Language.upper() == 'JP':
print('こんにちは')
elif Language.upper() == 'EN':
print('Hello')
else:
print('JP, ENのいずれかを入力してください')
def printsys():
print(sys.argv)
def shownameval():
print(__name__)
shownameval()
1-2.モジュールのインポート:import module
モジュールのインポート方法は下記の通りです。
1-3.モジュールの使用方法
モジュールの使用方法はインポート後にmodule.属性名で使用できます。使用可能な属性名はdir()で確認できます(下記参照)。
[In] Jupyterで実行
import sayhello
print(type(sayhello))
print(dir(sayhello))
print(sayhello.varhello_JP)
sayhello.Hello('EN')
[Out]
<class 'module'>
['Hello', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'printsys', 'shownameval', 'sys', 'varhello_JP']
変数:こんにちは
Hello
1-4.ターミナルで直接実行
ターミナルで直接実行する場合はモジュール内に実行したい関数定義だけでなく関数の実行(func())を直接記載することで可能となります。
1-4-1.ターミナルで実行: python module.py
ターミナルにおいて「python file名」でモジュールを実行できます。※python2と3が混在している時は「python3 file名」となります。
上記”sayhello”モジュールでは”shownameval()”が自動で実行されます。
[Terminal]
python sayhello.py
1-4-2.ターミナルで引数を渡す:sys.argv
ターミナル経由でsys.argvを使用するとリスト形式で['モジュール名']が渡され、引数を記載すると['モジュール名', '引数']で渡されます。よってターミナルから引数を取得したい場合はsys.argv[1]となります。
参考としてモジュールを下記に修正します。
[In] sayhello.py
import sys
varhello_JP='変数:こんにちは'
def Hello(Language: str):
if Language.upper() == 'JP':
print('こんにちは')
elif Language.upper() == 'EN':
print('Hello')
else:
print('JP, ENのいずれかを入力してください')
def printsys():
print(sys.argv)
def shownameval():
print(__name__)
printsys()
1-5.直接実行時のみ動く:if __name__=='__main__':
1-1で作成したコードには欠陥がありモジュールを読み込んだ時点で関数が必ず処理されます。参考として下記の通りJupyter側でモジュールをインポートだけしてみるとモジュールに記載した関数が自動で実行されました。
モジュールのインポート時には関数を実行せずモジュールを直接実行時のみ関数を処理される方法として「if __name __== '__main__'」があります。
__name__引数には特殊引数であり下記のような動作になります。
つまりimport時には実行させずスクリプトとして実行させたい場合は「if __name __== '__main__'」と記述すると期待される実行結果が得られます。
[In]
import sys
varhello_JP='変数:こんにちは'
def Hello(Language: str):
if Language.upper() == 'JP':
print('こんにちは')
elif Language.upper() == 'EN':
print('Hello')
else:
print('JP, ENのいずれかを入力してください')
def printsys():
print(sys.argv)
def shownameval():
print(__name__)
if __name__ == '__main__':
shownameval()
[Out]
import時にshownameval()が自動で出力されない
2.パッケージ(ライブラリ)
複数のモジュールが管理されたディレクトリをパッケージと呼びます。ディレクトリ内に用途ごとに作成したモジュールを配置することでまるで一つのモジュールの様に扱うことができます(参考例は下記の通り)。
今回はKIYOパッケージを作成します。初めに"KIYO"としたフォルダを作成しました。このフォルダ内にモジュールを入れてパッケージを作成します。
2-1.パッケージ作成の初期:__init__.pyの配置
一般的にパッケージを作成する場合は__init__.pyを配置します。こちらは空ファイルでも問題ありませんが、下記機能があり有効活用ができます。
パッケージ用フォルダに__init__.pyを空ファイルで配置しました。これだけでモジュールとして読み込みが可能です。属性にはモジュールには無かた”__path__”が追加されています。
[In]
import KIYO
print(type(KIYO))
print(dir(KIYO))
print(KIYO.__path__)
[Out]
<class 'module'>
['__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
_NamespacePath(['c:\\Users\\KIYO-\\Desktop\\note\\note_モジュール\\KIYO', 'c:\\Users\\KIYO-\\Desktop\\note\\note_モジュール\\KIYO'])
2-2.モジュールの配置
パッケージ内に__init__.pyとは別に使用したいモジュールを配置します。
今回は適当なモジュールを3つ作成しました。
※helloworld.py内の__all__については後述。
[KIYO/helloworld.py]
def sayhello(Language: str):
if Language.upper() == 'JP':
print('こんにちは')
elif Language.upper() == 'EN':
print('Hello')
else:
print('JP, ENのいずれかを入力してください')
def addnums(a: int, b: int) -> int:
return a + b
def multiplynums(a: int, b: int) -> int:
return a * b
__all__ = ['sayhello']
[KIYO/printsys.py]
import sys
def printsys():
print(sys.argv)
[KIYO/showname.py]
def shownameval():
print(__name__)
2-3.パッケージのインポート
パッケージのインポート方法は読み込む内容で要領が異なります
足りていないと思うけど図解すると下記の通りです。
なおパッケージのインポートは下記順序で実施されます。用語はよくわからなくても「Python 標準ライブラリと同じ名前のモジュールは作成しない」ことを注意すれば大きなトラブルは防げます。
2-4.パッケージの使用方法
__init__.pyはまだ空ファイルですがモジュールをインポートしてみます。結果として下記のことが確認できました。
[IN]
import KIYO
from KIYO import showname
from KIYO.helloworld import *
from KIYO.helloworld import addnums
print(dir(KIYO)) #KIYOモジュールの属性を確認
print(dir()) #現在のモジュールの属性を確認
#モジュールの関数を実行
showname.shownameval() #shonameモジュールのshownameval関数を呼び出す
KIYO.helloworld.sayhello('EN') #モジュール内の属性をワイルドカード(*)で呼び出したため使用可能
addnums(1,2)
#下記はエラー発生コード
# shownameval() #関数だけ記載するのはNameError
# KIYO.printsys.printsys() #この書き方はAttributeError
[Out]
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'helloworld', 'showname']
['In', 'KIYO', 'Out', '_', '_1', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '__vsc_ipynb_file__', '_dh', '_i', '_i1', '_i2', '_ih', '_ii', '_iii', '_oh', 'addnums', 'exit', 'get_ipython', 'multiplynums', 'os', 'quit', 'sayhello', 'showname', 'site', 'sys']
KIYO.showname
Hello
3
2-5.import * での呼び出し属性の制限:__all__
import *ですべての属性を呼び出せますが下記問題もあります。
①dir()で確認すると不要な属性が出るため見にくい
②他モジュールと属性が被ることで期待しない動作が起こる
上記問題を避けるために呼び出すことができる属性を制限できます。制限としては__all__引数に指定する属性をリスト形式で渡します。
既にhelloworldモジュールには__all__引数を指定済みのため下記を実行するとモジュール内に記載したはずの関数が使用できないことが確認できます。
[In]
from KIYO.helloworld import *
print(dir())
sayhello('JP')
multiplynums(1,2) #NameError:NameError: name 'multiplynums' is not defined
[Out]
['In', 'Out', '_', '__', '___', '__builtin__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', '__vsc_ipynb_file__', '_dh', '_i', '_i1', '_i2', '_ih', '_ii', '_iii', '_oh', 'exit', 'get_ipython', 'os', 'quit', 'sayhello', 'site', 'sys']
こんにちは
NameError
3.__init__.pyの活用
前述の通り__init__.pyは下記用途があります。
パッケージをさらに便利に使用できるように__init__.pyを利用します。
3-1.__init__.pyの動作確認:import時に自動実行
まずは__init__.pyの動作を確認します。__init__.pyに下記コードを記載して(再起動して)パッケージをimportすると自動で__init__.pyが実行されました。
[__init__.py]
print(f'__init__.pyが実行されたよ')
print(f'__name__={__name__}')
[In]
import KIYO
[Out]
__init__.pyが実行されたよ
__name__=KIYO
3-2.相対インポートによる属性の取得
前述の通り空__init__.pyではimport packageだけだと何も使用できませんでした。package.属性で参照できるAPIを作りたい場合は__init__.pyに相対インポートでモジュール内の属性を記載することでAPIを実現できます。
相対インポートとは位置関係を示す記述でありmodule名の前に.を記載すると現在のパッケージを意味します。
[__init__.pyが空ファイル]
[In ※実行ファイル]
import KIYO
print(dir(KIYO))
[Out]
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__']
[__init__.py]
from .showname import shownameval
from .printsys import printsys
[In]
[In ※実行ファイル]
import KIYO
print(dir(KIYO))
KIYO.shownameval()
KIYO.printsys()
[Out]
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'printsys', 'showname', 'shownameval']
KIYO.shownameval()
※['C:\\Users\\KIYO-\\anaconda3\\lib\\site-packages\\ipykernel_launcher.py',以下省略]
3-3.__all__+ワイルドカード(*)を同時利用
ワイルドカード(*)でインポートする属性を指定する"__all__"を設定しておき、"__init__.py"をfrom .module import *とすることで"__init__.py"は更新不要となります。
[__init__.py]
from .showname import shownameval
from .printsys import printsys
from .helloworld import *
[helloworld.py]
def sayhello(Language: str):
if Language.upper() == 'JP':
print('こんにちは')
elif Language.upper() == 'EN':
print('Hello')
else:
print('JP, ENのいずれかを入力してください')
def addnums(a: int, b: int) -> int:
return a + b
def multiplynums(a: int, b: int) -> int:
return a * b
__all__ = ['sayhello']
[In 実行ファイル]
import KIYO
print(dir(KIYO))
KIYO.sayhello('EN') #__all__で設定済み
KIYO.addnums(1,2) #__all__で設定していない->AttributeError
[Out]
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'helloworld', 'printsys', 'sayhello', 'showname', 'shownameval']
Hello
AttributeError
結果として"helloworld.py"モジュールは3つの関数がありますが"__all__"設定によるワイルドカードで呼び出しても指定した属性しか使用できません。
4.補足説明1:相対インポート
3章で相対インポートを使用して__init__.pyを利用することでパッケージに属性を取得しましたが相対インポートについてさらに詳しく確認します。
4-1.親フォルダ内モジュールのインポート
相対参照より".."だと親フォルダを意味するためfrom .. import moduleの様に記載するとエラーが発生します。実施時はsys.pathへの追加が必要です。
[In ※KIYO/child.ipynbで実行 ※parent.pyはKIYOフォルダ内に配置]
from .. import parent
[Out]
ImportError: attempted relative import with no known parent package
4-2.パッケージ内モジュール:from . import module
同じパッケージ内のモジュールからimportする場合は1章と考え方が異なります。テストでKIYO2パッケージ内にA.py, B.pyを作成してimport Aでモジュールを読み込みましたが「ModuleNotFoundError 」が発生します。
詳細は下記記事に記載しておりますが「パッケージ内のモジュールをインポートするときはfrom . import modueと記載」します。
[KIYO2/A.py]
varA = 'Aの変数'
class A:
varA = 'Aのクラス変数'
def __init__(self):
self.varA = 'Aのインスタンス変数'
A_ins = A()
[KIYO2/B.py]
from . import A #相対インポートでないとエラー
vBrB = 'Bの変数'
class B:
varB = 'Bのクラス変数'
def __init__(self):
self.varB = 'Bのインスタンス変数'
def checkAB():
print(f'A.varA = {A.varA}, B.varB = {B.varB}')
print(A.A_ins.varA)
[In]
from KIYO2 import A, B
#Aのクラス変数とインスタンス変数の取得確認
B.checkAB() #B.pyがA.pyをインポートしてクラス変数を出力
[Out]
A.varA = Aの変数, B.varB = Bのクラス変数
Aのインスタンス変数
5.まとめ(自分用)
自分が使用するための箇条書きとなります。
参考資料
dir()関数の参考
あとがき
自分でライブラリなんて作ることはないと思ってたけど、特定のライブラリを使用するために最低限モジュールの知識は必要だったのでとりあえずまとめました。
追記1@2022年4月3日:importって使うと楽(に使えるようにしてもらっている)だけど作ろうとするとこんなに難しいんだ・・・・・
この記事が気に入ったらサポートをしてみませんか?