note.nkmk.me

Pythonのargparseでブール値を扱うときは注意が必要

Date: 2017-08-28 / tags: Python

Pythonでコマンドライン引数を扱うには、sysモジュールのargvかargparseモジュールを使う。

argparseモジュールを使うとコマンドライン引数を柔軟に処理できるが、ブール値(True, False)を扱う場合は注意が必要。

以下の内容について説明する。

  • 引数を簡単に定義できるargparse
  • argparseで引数の型(type)を指定
  • add_argument()の引数typeboolを指定してはいけない
  • bool()による判定
  • 引数typeではなく引数actionを使う
  • 関数strtobool()を使う
スポンサーリンク

引数を簡単に定義できるargparse

argparseモジュールを使うと、コマンドラインの引数を簡単に定義できる。

argparse モジュールはユーザーフレンドリなコマンドラインインターフェースの作成を簡単にします。プログラムがどんな引数を必要としているのかを定義すると、argparse が sys.argv からそのオプションを解析する方法を見つけ出します。argparse モジュールは自動的にヘルプと使用方法メッセージを生成し、ユーザーが不正な引数をプログラムに指定したときにエラーを発生させます。 16.4. argparse — コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.6 ドキュメント

argparseで引数の型(type)を指定

argparseで便利なのが、型(type)の指定。

例えば、整数(int)型を指定しておくと、引数を自動でintに変換してくれて、さらにintではない引数に対してエラーが発生するようになる。

add_argument()の引数typeで型を指定する。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('arg_int', type=int)

args = parser.parse_args()
print(args.arg_int)
print(type(args.arg_int))

このファイルをコマンドラインから実行する。

$ python argparse_type_int.py 100
100
<type 'int'>

引数100がintとして読み込まれている。

intではない値を引数とすると、エラーが発生する。

$ python argparse_type_int.py foo
usage: argparse_type_int.py [-h] arg_int
argparse_type_int.py: error: argument arg_int: invalid int value: 'foo'

$ python argparse_type_int.py 1.23
usage: argparse_type_int.py [-h] arg_int
argparse_type_int.py: error: argument arg_int: invalid int value: '1.23'

想定外の引数を弾けるのでとても便利。

add_argument()の引数typeにboolを指定してはいけない

注意が必要なのがboolintfloatなどのように、add_argument()の引数typeboolを指定すると、想定通りの動作をしてくれない。

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('arg_bool', type=bool)

args = parser.parse_args()
print(args.arg_bool)
print(type(args.arg_bool))

このファイルをコマンドラインから実行する。

$ python argparse_type_bool.py True
True
<type 'bool'>

Trueを引数とすると、bool型のTrueとして読み込まれる。これは想定通りの動作だが、問題は次の場合。

$ python argparse_type_bool.py False
True
<type 'bool'>

$ python argparse_type_bool.py bar
True
<type 'bool'>

Falseや他の文字列を引数にしてもTrueとして読み込まれてしまう。

なぜこのようなことになってしまうかというと、add_argument()type=xxxと指定すると、引数がxxx()に渡されるから。

例えば、type=intとすると、引数が、int型のコンストラクタ`int()に渡される。type=floatの場合はfloat()

type=boolの場合も同じで、引数がbool()に渡されることになる。

bool()による判定

このbool()が曲者。

以下の値は偽と見なされます:

  • None
  • False
  • 数値型におけるゼロ。例えば 0, 0.0, 0j 。
  • 空のシーケンス。例えば '', (), [] 。
  • 空のマッピング。例えば {} 。

それ以外の全ての値は真と見なされます — 従って、多くの型のオブジェクトは常に真です。ブール値の結果を返す演算および組み込み関数は、特に注釈のない限り常に偽値として 0 または False を返し、真値として 1 または True を返します。

したがって、'True'だろうが'False'だろうが、空ではない文字列をbool()に渡すと、すべてTrueが返ってくる。空文字列のみがFalseとなる。

print(bool('True'))
print(bool('False'))
print(bool('abc'))
# True
# True
# True

print(bool(''))
# False
source: bool_test.py

add_argument()type=boolとすると引数がbool()に渡されるため、上の例のように、Falseを引数とした場合は文字列'False'としてbool()で変換され、Trueとして読み込まれてしまう。

bool型についての詳細は以下の記事を参照。

引数typeではなく引数actionを使う

argparseでブール値を使いたい場合は、typeではなく引数action'store_true'または'store_false'を指定する。

'store_true', 'store_false' - これらは 'store_const' の、それぞれ True と False を格納する特別版になります。加えて、これらはそれぞれデフォルト値を順に False と True にします。 16.4. argparse — コマンドラインオプション、引数、サブコマンドのパーサー — Python 3.6 ドキュメント

import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--en', action='store_true')

args = parser.parse_args()
print(args.en)
print(type(args.en))

この例の場合、オプション--enをつけるとenTrueとして、つけないとenがデフォルト値のFalseとして読み込まれる。

$ python argparse_option_bool.py --en
True
<type 'bool'>

$ python argparse_option_bool.py
False
<type 'bool'>

デフォルトをTrueにして、オプションを付けたときにFalseとしたい場合は、action='store_false'とすればOK。

関数strtobool()を使う

オプションではなく位置引数を使いたい場合は、関数strtobool()を使う方法もある。

strtobool()は文字列を真(1)または偽(0)に変換する関数。

真偽値をあらわす文字列を真(1)または偽(0)に変換します。真の値は y, yes, t, true, on そして 1 です。偽の値は n, no, f, false, off そして 0 です。 val が上のどれでもない時は ValueError を起こします。 10. API リファレンス — Python 3.6.1 ドキュメント strtobol()

文字列'y', 'yes', 'true', 'on', '1'は真(1)、文字列'n', 'no', 'f', 'false', 'off', '0'は偽(0)を返す。大文字小文字は関係ないので'TRUE''True, 'YES'などでもOK。それ以外の文字列はエラーになる。

from distutils.util import strtobool

print(strtobool('true'))
print(strtobool('True'))
print(strtobool('TRUE'))
# 1
# 1
# 1

print(strtobool('t'))
print(strtobool('yes'))
print(strtobool('y'))
print(strtobool('on'))
print(strtobool('1'))
# 1
# 1
# 1
# 1
# 1

print(strtobool('false'))
print(strtobool('False'))
print(strtobool('FALSE'))
# 0
# 0
# 0

print(strtobool('f'))
print(strtobool('no'))
print(strtobool('n'))
print(strtobool('off'))
print(strtobool('0'))
# 0
# 0
# 0
# 0
# 0

# print(strtobool('abc'))
# ValueError: invalid truth value 'abc'

strtobool()という名前だが、返り値はbool型ではなくint型(1または0)。

print(type(strtobool('true')))
# <class 'int'>

先に書いたように、argparseのadd_argument()type=xxxと指定すると引数がxxx()に渡されるようになるので、type=strtoboolとすればよい。

import argparse
from distutils.util import strtobool

parser = argparse.ArgumentParser()
parser.add_argument('arg_bool', type=strtobool)

args = parser.parse_args()
print(args.arg_bool)
print(type(args.arg_bool))

boolではなく、int10ではあるが、truefalseを引数として真偽の値を読み込むことができる。

$ python argparse_type_strtobool.py true
1
<type 'int'>

$ python argparse_type_strtobool.py false
0
<type 'int'>

また、想定外の引数の場合はちゃんとエラーが発生する。

$ python argparse_type_strtobool.py bar
usage: argparse_type_strtobool.py [-h] arg_bool
argparse_type_strtobool.py: error: argument arg_bool: invalid strtobool value: 'bar'
スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事