どういったときに使うのか
自作したpythonファイル内に大量の関数があり,それらを個別で使用したいが,そのために大量の実行用ファイルを作りたくない,複雑な引数制御を書きたくない,というとき。
例えば,通信試験を行うとき,大量の通信コマンドを任意のタイミングで動かす必要があるため,手動でコマンドライン操作を行うことになると思いますが,そういったときに,もしかしたら本記事の内容が役に立つかもしれません。
本記事の環境
そこまで気にしなくてもいいと思いますが,一応書いておきます。
- Windows10
- Python 3.7.3
- git-bash 4.4.23
ファイルの準備
解説用に次のpyファイルを作ります。引数のないhello
関数と2つの引数があるadd
関数が書かれています。これをターミナルから実行してみます。
def hello():
print('hello qiita')
def add(a, b):
print(a + b)
方法1: pythonのcmdオプションを使う
典型的な方法として,pythonのcmdオプションを使う方法があります。
python -c <command>
pythonを実行するとき,上記のように-c
をつけることで後ろのコマンドを実行できます。コマンド内での改行は;
で表現します。
これを使って上記のpyファイルの関数を呼び出してみましょう。
python -c "import call_test; call_test.hello()"
hello qiita
hello関数が実行されていることがわかると思います。次に,引数がある関数を実行します。
a=1
b=2
python -c "import call_test; call_test.add($a,$b)"
3
pyファイルを編集せずに済むので,最もよく使われる方法だと思います。ただし,importを書く必要があるため,コマンドが冗長になりやすいという欠点があります。
方法2 : 汎用的なmain関数を作る
状況によっては,やっぱりmain関数を作ったほうがいい場合もあると思います。例えば,初期化処理がimportだけでは済まず,長大なコマンド文が必要になる場合,関数の戻り値に応じた制御が必要な場合,などです。
このとき大事なことは,関数名をmain関数のソースコード上にべた書きしないということです(そこにミスがあったら何の試験をやっているのかわからなくなる)
説明用にmain関数を作りました。先ほどのcall_test.py
に以下のmain関数を追加してください。標準的な引数(任意個数のfloat
やstr
)には対応するように書いたので,割と使いまわせると思います。
import argparse
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument('function_name',
type=str,
help='set fuction name in this file')
parser.add_argument('-i', '--func_args',
nargs='*',
help='args in function',
default=[])
args = parser.parse_args()
# このファイル内の関数を取得
func_dict = {k: v for k, v in locals().items() if callable(v)}
# 引数のうち,数値として解釈できる要素はfloatにcastする
func_args = [float(x) if x.isnumeric() else x for x in args.func_args]
# 関数実行
ret = func_dict[args.function_name](*func_args)
使い方は次のようになっています。ファイル名と関数名を並べて指定し,関数内引数が存在する場合は-i
オプションの後に指定します。
python <file.py> <関数名> -i <関数内引数>
例えば,先ほどの-c
オプションの解説と同様のことは,次のようにできます。
# call_test.py の hello() を実行
python call_test.py hello
# call_test.py の add(1,2) を実行
python call_test.py add -i 1 2
可読性は,やっぱりこっちのほうがいいかなと思います。
さいごに
実はrunpパッケージといったpythonの関数をターミナルから呼び出すことに特化したpythonパッケージがあります。runp
はpipで入れられたのですが,残念なことに,python3系には対応していないようでした。3系に対応したrunpのようなパッケージをどなたかご存じであれば,教えてください(時間があれば自分で作りたいけど…)