Argparseを使ったコマンドライン引数の管理

通常コマンドライン引数を扱うにはsys.argvを使います。

import sys

args = sys.argv
count = args[1]

print(count)
$ py main.py 10
10

とてもシンプルですね。

ただ実際にちゃんとしたプログラムを組む場合、例えばコマンドライン引数が渡されなかった場合に例外にしたり、もしくはデフォルト値を設定したり、ハイフン始まりのオプション引数を用意したり、そういうことをやろうと思うとどんどん複雑になっていきます。

Argparseを使えばそういうったものをとても楽に実装することができます。

Argparse チュートリアル — Python 3.6.5 ドキュメント

とりあえず基礎的なものをいくつか実例で動作確認してみます。

ヘルプだけ用意された最小の例

import argparse
parser = argparse.ArgumentParser()
parser.parse_args()
$ py main.py -h
usage: main.py [-h]

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

$ py main.py --help
usage: main.py [-h]

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

特に何も設定してない状態でも「-h」と「--help」が設定されます。

省略可能な引数を一つ設定する、省略時はデフォルト値が使われる。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument("count", nargs='?', default=0)
args = parser.parse_args()

print(args.count)
$ py main.py
0

$ py main.py 10
10

nargsに'?'を設定すれば省略可能な引数を定義できます。defaultで省略時の値を設定できます。

オプション引数を設定する。

「-f」のようなハイフンで始まる独自のオプション引数を設定してみます。

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--foo")
args = parser.parse_args()

print(args.foo)
$ py main.py -h
usage: main.py [-h] [-f FOO]

optional arguments:
  -h, --help         show this help message and exit
  -f FOO, --foo FOO

helpを見ると-fが追加されてるのが分かりますね。

$ py main.py -f 100
100

$ py main.py -f100
100

$ py main.py -f=100
100

この用に値を渡せます。

スペース空けたり空けなかったり、「=」を付けたり付けなかったりしてもどちらでも動作します。

型の変換をする

コマンドライン引数は全て文字列型になります。

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--foo")
args = parser.parse_args()

print(type(args.foo))
$ py main.py -f 100
<class 'str'>

これを例えば初めからint型にするにはtypeを使います

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("-f", "--foo", type=int)
args = parser.parse_args()

print(type(args.foo))
print(args.foo)
$ py main.py -f 100
<class 'int'>
100

この状態でもし文字列を渡したりすると例外になります

$ py main.py -f aaa
usage: main.py [-h] [-f FOO]
main.py: error: argument -f/--foo: invalid int value: 'aaa'


またtypeは単に関数を指定するだけなので独自の関数を指定することで任意の変換処理をさせることができます。

import argparse

def foo(s):
    return "["+s+"]";

parser = argparse.ArgumentParser()
parser.add_argument("-f", "--foo", type=foo)
args = parser.parse_args()

print(args.foo)
$ py main.py -f aaa
[aaa]

フラグ系のオプション引数を設定する。

値を設定する必要がない真偽値だけを持つオプション引数を設定してみます。

import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--bar", action="store_true")
args = parser.parse_args()

print(args.bar)
$ py main.py
False

$ py main.py --bar
True


他にも各引数にコメントを付けたりだとか色々機能はありますが、とりあえずこの辺りだけ覚えておけば実用に供すると思います。

https://docs.python.jp/3/library/argparse.html

subprocessを使って外部プログラムを実行する

https://docs.python.jp/3/library/subprocess.html

subprocessには色々な関数が定義されているのでとりあえずいくつか使いそうなものを試してみます。

例としてPythonから別のPythonのプログラムを実行する処理を実装してみます。

subprocess.check_callによる簡易的な外部プログラムの実行

まずtest.pyを用意します。

# -- test.py --
from time import sleep

print('child start!')
sleep(1)
print('child end!')

これをmain.pyから実行してみます。

# -- main.py --
import subprocess

print('parent start!')
subprocess.check_call(['python','test.py'])
print('parent end!')
$ py main.py
parent start!
child start!
child end!
parent end!

check_allは子プロセス(test.py)の終了をまってから次の処理へ進みます。

また外部プログラム側で何かしら例外で終了した場合はcheck_callでも例外を投げます。

# -- error.py --
raise Exception('error test!')
# -- main.py --
import subprocess

print('parent start!')
subprocess.check_call(['python','error.py'])
print('parent end!')
$ py main.py
parent start!
Traceback (most recent call last):
  File "error.py", line 1, in <module>
    raise Exception('error test!')
Exception: error test!
Traceback (most recent call last):
  File "main.py", line 8, in <module>
    ps = subprocess.check_call(['python','error.py'])
  File "F:\Python36\lib\subprocess.py", line 291, in check_call
    raise CalledProcessError(retcode, cmd)
subprocess.CalledProcessError: Command '['python', 'error.py']' returned non-zero exit status 1.

subprocess.check_outputで実行されたプログラムの標準出力を得る

check_callと違い、こちらは外部プログラムで実行された標準出力を実行側で文字列で取得することができます。

# -- test.py --
from time import sleep

print('child start!')
sleep(1)
print('child end!')
# -- main.py --
import subprocess

output = subprocess.check_output(['python','test.py'])

print('---')
print(output.decode('sjis'))
$ py main.py
---
child start!
child end!

ちなみにcheck_outputの戻り値はバイナリ文字列になってるのでdecodeで文字列化してから出力してます。

subprocess.runによる外部プログラムの実行

Python3.5以上からはcheck_callやcheck_outputよりも柔軟なrunを使うこともできます。

check_callをrunで置き換えると以下のようになります。

import subprocess

print('parent start!')
subprocess.run(['python','test.py'], check=True)
print('parent end!')


check_outputをrunで置き換えると以下のようになります。

import subprocess

output = subprocess.run(['python','test.py'], check=True, stdout=subprocess.PIPE).stdout
print(output.decode('sjis'))

他にも色々できるようですが、ここでは割愛。

目的がハッキリしている場合はcheck_callやcheck_outputでも良さそうですね。

subprocess.Popenによる詳細な外部プログラムの実行

通常ではcheck_allやcheck_output(およびrun)などを使えばそれで基本的には問題がでることはありませんが、例えば子プロセスの終了をまたずに何か処理をしたいとかそういうことをしたい場合にはPopenを使います。

# -- test.py --
from time import sleep

print('child start!')
sleep(1)
print('child end!')
# -- main.py --
import subprocess

print('parent start!')
ps = subprocess.Popen(['python','test.py'])
print('parent end!')
$ py main.py
parent start!
parent end!
child start!
child end!

このように子プロセスの終了をまたずに「parent end!」が出力されていますね。

with文を利用したファイルの読み書き

例えば以下のようなファイルの読み込み処理があるとします。

fh = open('text.txt','rt')
text = fh.read()
fh.close()

with文を使うと以下のようになります

with open('text.txt','rt') as fh
    text = fh.read()

with文を使うとcloseを明示的に呼ぶ必要がなくなります。

with文のスコープを抜けた時点で自動的に呼ばれます。

便利なので基本的にはファイルの入出力では使った方が良いですね。

with文自体の仕様についてはまた今度。

Pythonでのファイル読み書き基礎

入門Python3 8.1


ファイルの読み書きです。

まずはテキストデータの書き込み処理。

openの第二引数にwを指定すると書き込みとなります。tはテキストモードでこれをbにすればバイナリモードのなります。

fh = open('output.txt','wt')
fh.write('hogehoge')
fh.close()
$ py main.py


これでoutput.txtが作成されます。

# -- output.txt --
hogehoge


次はテキストデータの読み込みです。

openの第二引数にrを指定すると読み込みとなります。

fh = open('output.txt','rt')
text = fh.read()
fh.close()

print(text)
$ py main.py
hogehoge

Pythonでsleep

https://docs.python.jp/3/library/time.html#time.sleep

timeモジュールのsleep関数を使います

from time import sleep

print(1)
sleep(1)
print(2)
$ py main.py
1
1

sleep(1)で1秒スリープする感じです。

float値でもいけるので0.5秒スリープしたりとかもできます。

attrdictを使って辞書をオブジェクト変数としてアクセスできるように変換する

例えばPythonJSONを読み込むとそれは辞書として変換される

import json

data = json.loads('{"a": 1,"b": {"c": 4} }')
print(data)
$ py main.py
{'a': 1, 'b': {'c': 4}}


辞書なので各要素にアクセスする時は以下のようになる。

import json

data = json.loads('{"a": 1,"b": {"c": 4} }')
print(data["b"]["c"])
$ py main.py
4

ただいちいち[]つけて書くのがめんどくさい。やっぱdata.b.cとアクセスできた方が楽だし書きやすいよね。

というわけでattrdictというライブラリを使うことでそれが可能となります。

https://pypi.python.org/pypi/attrdict/2.0.0

まずはインストール

$ pip install attrdict


利用例

import json
from attrdict import AttrDict

data = AttrDict(json.loads('{"a": 1,"b": {"c": 4} }'))
print(data.b.c)
$ py main.py
4

便利ですね。