モジュールとして使われるよう実装した複数のPythonスクリプトをパッケージにまとめる方法の一例について備忘録。突然に仕事が忙しくなり時間に追われながら勉強したメモなので間違いあるかも。親切な方、間違いあればご指摘いただけると幸いです。。
カンマ区切りテキストファイル(CSV)とタブ区切りテキストファイル(TSV)を扱うパッケージを作るとする。その実装は極めて似ているので、共通のベースクラスを作成してそれを継承する形で実装するとしよう。したがってCSV用とTSV用の2つのクラスだけを公開できれば良い。この場合、たとえば次のようにファイル・ディレクトリを作成する。
mymodule/
__init__.py
csvfile.py
tsvfile.py
tabulartextfile.py
各ファイルの内容は次の通り。
#-----------------------------------------------------------
# mymodule/__init__.py
from .csvfile import CsvFile # mymodule.csvfile.CsvFile --> mymodule.CsvFile でアクセス可能にする
from .tsvfile import TsvFile # mymodule.tsvfile.TsvFile --> mymodule.TsvFile でアクセス可能にする
#-----------------------------------------------------------
# mymodule/tabulartextfile.py
class TabularTextFile:
def __init__(self, filename, mode='rt', separator=','):
self.file = open(filename, mode)
self.separator = separator
def readlines(self):
for line in self.file.readlines():
yield line.split(self.separator)
#-----------------------------------------------------------
# mymodule/csvfile.py
from mymodule import TabularTextFile
class CsvFile(TabularTextFile):
def __init__(self, filename, mode='rt'):
super.__init__(filename, mode, ',')
#-----------------------------------------------------------
# mymodule/Tsvfile.py
from mymodule import TabularTextFile
class TsvFile(TabularTextFile):
def __init__(self, filename, mode='rt'):
super.__init__(filename, mode, '\t')
こうすると、次のようにCsvFile
とTsvFile
クラスにアクセスでき、TabularTextFile
はインポートされない形になる:
$ python
>>> from mymodule import *
>>> CsvFile
<class 'mymodule.csvfile.CsvFile'>
>>> TsvFile
<class 'mymodule.tsvfile.TsvFile'>
>>> TabularTextFile
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'TabularTextFile' is not defined
なおTabularTextFile
はアクセス禁止というわけではなく、from XXX import *
でのインポート対象にならないだけ。したがってモジュールを普通にインポートすると普通にアクセスできる。
$ python
>>> import mymodule
>>> mymodule.tabulartextfile.TabularTextFile
<class 'mymodule.tabulartextfile.TabularTextFile'>
また、パッケージを構成するモジュール間で互いにインポートする場合は相対インポートを使える。具体的には次のように書いても良い。
#-----------------------------------------------------------
# mymodule/csvfile.py
from .mymodule import TabularTextFile # 「.」で相対位置を指定してインポート
ただし、こうするとそのモジュール定義ファイルをスクリプトとして実行したり、それ単独でインポートしたりすることができなくなる。したがって、モジュールの簡易テスト用コードをif __name__ == "__main__":
で書いているような場合、それを実行できなくなる。具体的には:
$ find . -name '*.py'
./mymodule/csvfile.py
./mymodule/tabulartextfile.py
./mymodule/tsvfile.py
./mymodule/__init__.py
$ python mymodule/csvfile.py
Traceback (most recent call last):
File "mymodule\csvfile.py", line 1, in <module>
from .tabulartextfile import *
SystemError: Parent module '' not loaded, cannot perform relative import
$ cd mymodule
$ python csvfile.py
Traceback (most recent call last):
File "csvfile.py", line 1, in <module>
from .tabulartextfile import *
SystemError: Parent module '' not loaded, cannot perform relative import
$ python
>>> import csvfile
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Users\suguru\Source\module_test\mymodule\csvfile.py", line 1, in <module>
from .tabulartextfile import *
SystemError: Parent module '' not loaded, cannot perform relative import
相対インポートを使った.pyファイルをスクリプトとして直接実行することは基本的にできないので、絶対インポート(=ディレクトリ階層の制約が生まれる)に統一するか、if __name__ == "__main__":
でテストコードを書かないようにするのがPython的には望ましい、というところだろうか。