paloma blog

NWエンジニアやってます。主に自宅環境のお遊びを書きます。Pythonもちょっと。タイトルは好きなカクテルから。

pythonでコマンドライン用の引数処理をモジュールから使う

自作の映画情報投稿ツールをスマホアプリとして作れないかとやってるんですが未知の領域の部分には困りごとが出てきます。

映画のタイトルや公開年をshellの引数で取るようにしているのですがスマホアプリで使うならそうはいきません。
専用に修正すればなんてことないのですがモジュールとして使うからにはそのまま使いたいところ。
元のコードを直せば...と言っているとキリがありませんのでコード内で収まらないか色々試したところ出来たのでメモしておきます。

引数用の属性を探せ

引数処理はargparseを使っています。
まずはテスト用のサンプルコード。

(movies) masashi@PC-ubuntu:~$ cat argtest.py 
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--year', type=int)
args = parser.parse_args()

def main():
    if args.year:
        print('{}年宇宙の旅'.format(args.year))
    else:
        print('2001年宇宙の旅')

if __name__ == '__main__':
    main()

シェルから使うとこんな感じ。

(movies) masashi@PC-ubuntu:~$ python argtest.py 
2001年宇宙の旅

(movies) masashi@PC-ubuntu:~$ python argtest.py -h
usage: argtest.py [-h] [--year YEAR]

optional arguments:
  -h, --help   show this help message and exit
  --year YEAR

(movies) masashi@PC-ubuntu:~$ python argtest.py --year 2022
2022年宇宙の旅

pythonシェルでテスト

バージョン。仮想環境内です。

(movies) masashi@PC-ubuntu:~$ python --version
Python 3.8.10

プロンプト内でimportします。

>>> import argtest

困ったらdir。

>>> dir(argtest)
['__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'argparse', 'args', 'main', 'parser']

argsという属性があります。
これは引数処理で使ったぞ。

中身。
argsにアクセスしたら引数として書いたyearの変数がありますね。 これを変えればいけそうな感じ。

>>> argtest.args
Namespace(year=None)

今はNoneなので何も返ってきません。

>>> argtest.args.year
>>> 

値を入れてみます。

>>> argtest.args.year = 3000
>>> argtest.args
Namespace(year=3000)

mainで実行。

>>> argtest.main()
3000年宇宙の旅

OK。変わりましたね。 再度関数化すればコード内でも使えそう。

実際のコードで試す

投稿した映画タイトルを呼んでみます。
自作モジュールのpathは追加済み。(呼び出し方法で少しはまりましたが割愛)

>>> import movietweet.gettw
>>> 
>>> movietweet.gettw.args
Namespace(year=None)

とりあえず直近10件だけ出すよう関数作ります。
エラーハンドリングは後で考えます。

>>> def gettitle(y):
...     movietweet.gettw.year = y
...     movies = movietweet.gettw.main()
...     for movie in movies[:10]:
...         print(movie['posted_date'], movie['title'])
... 
>>> 

実行。

>>> gettitle(2022)
2022-06-11 23:50:32 The Suicide Squad
2022-06-05 23:41:30 Spenser Confidential
2022-06-05 00:30:57 Carlito's Way
2022-05-29 22:53:19 Uncorked
2022-05-28 23:27:57 Ghost Dog: The Way of the Samurai
2022-05-22 22:54:41 Kong: Skull Island
2022-05-22 10:49:03 The Mummy Returns
2022-05-15 23:14:56 Superlopez
2022-05-15 10:15:04 The Interpreter
2022-05-08 21:35:03 Jefe

OK!うまいこと動きました。
この方法を組み込みたいと思います。

ちなみに視聴したこの10件だとUncorked(邦題: ワインは期待と現実の味)が一番面白かったかな。

本件で使ったリポジトリはこちらです。

github.com

おまけ

シェルからの引数はimportだとできないんじゃないかと思っていて、初めはrunpyというモジュールを使おうと思っていました。 属性へのアクセスは同じです。

docs.python.org

こっちはファイルをそのまま読み込むみたいな感じでしょうか。
辞書型で各属性が格納されるようです。

>>> import runpy
>>> runpy.run_module('argtest')['__name__']
'argtest'

argsへのアクセス結果は同じですね。

>>> runpy.run_module('argtest')['args']
Namespace(year=None)

しかしそのまま入れてもダメ。
runpyモジュールを都度実行しているからでしょうか。

>>> runpy.run_module('argtest')['args'] = 3000
>>> runpy.run_module('argtest')['main']()
2001年宇宙の旅

変数に格納したら値が記憶できました。

>>> title = runpy.run_module('argtest')
>>> title['args'].year = 3000
>>> title['main']()
3000年宇宙の旅

これで同結果が得られました。
しかしimportで出来ることがわかった以上こちらは使わなくても大丈夫でしょう。