技術的な話

【Python】absl-pyでコマンドライン引数を受け取る

引数を使用したい場合によく使うのでまとめます。

バリデーションなんかも出来るのでちゃんとコマンドライン引数を実装したい場合に重宝します。

長い間バージョン1.0.0でしたが、最近アップデート入ったんですね…知らなかった。(2022年9月現在)

公式サイト

Github

abseil.io(Python)

使用方法

基本的な使用方法

基本的な記載方法はこのような形です。

DEFINE_stringで文字列型の引数を定義しています。

引数は以下になっています。

DEFINE_string(引数名, デフォルト値, ヘルプ表示時の表示内容)

他にもオプションはありますが、この辺を抑えておけば問題無いです。

from absl import app
from absl import flags
from absl import logging

FLAGS = flags.FLAGS

flags.DEFINE_string('sample', None, 'sample argument.')

def main(unused_argv):
    del unused_argv
    
    logging.info(f'sample argument is {FLAGS.sample}.')
    
if __name__ == '__main__':
    app.run(main)

実行時は下記のようになります。

引数の後にスペース(若しくはイコール)を入れて、渡したい値を記載します。

値を指定しない場合はデフォルト値が設定されます。

$ python main.py
I0903 11:35:52.920497 139623989774144 base.py:12] sample argument is None.

$ python main.py --sample test
I0903 11:35:52.920497 139623989774144 base.py:12] sample argument is test.

$ python main.py --sample=test
I0903 11:36:00.978282 140537450886976 base.py:12] sample argument is test.

必要に応じたオプション

引数定義時に以下オプションを付与することも可能です。

  • 短縮名
  • 必須フラグ

from absl import app
from absl import flags
from absl import logging

FLAGS = flags.FLAGS

flags.DEFINE_string('string_arg', None, 'sample string argument.')
flags.DEFINE_string('string_with_short_name_arg', None, 'sample string with short name argument.', short_name='s')
flags.DEFINE_string('string_required_arg', None, 'sample string required argument.', required=True)

def main(unused_argv):
    del unused_argv

    logging.info(f'sample string argument is {FLAGS.string_arg}.')
    logging.info(f'sample string short name argument is {FLAGS.string_with_short_name_arg}.')
    logging.info(f'sample string required argument is {FLAGS.string_required_arg}.')
    
if __name__ == '__main__':
    app.run(main)

実行時は下記のようになります。

必須の引数を省略するとエラーメッセージが表示されます。

$ python main.py --string_arg string_arg -s short_arg --string_required_arg required_arg
I0903 14:47:20.210859 140037052639040 options.py:17] sample string argument is string_arg.
I0903 14:47:20.210930 140037052639040 options.py:18] sample string short name argument is short_arg.
I0903 14:47:20.210959 140037052639040 options.py:19] sample string required argument is required_arg.

$ python main.py --string_arg string_arg -s short_arg
FATAL Flags parsing error: flag --string_required_arg=None: Flag --string_required_arg must have a value other than None.
Pass --helpshort or --helpfull to see help on flags.

比較的使用する引数

比較的使用する引数を列挙しました。

  • DEFINE_string:文字列型
  • DEFINE_integer:数値型
  • DEFINE_multi_integer:数値リスト型
  • DEFINE_float:浮動小数点型
  • DEFINE_multi_float:浮動小数点リスト型
  • DEFINE_boolean:論理型
  • DEFINE_list:文字列リスト型(カンマ区切り)
  • DEFINE_spaceseplist:文字列リスト型(スペース区切り)

DEFINE_listDEFINE_spaceseplistは型は同じですが、コマンドライン引数指定時の方法が少し異なります。

from absl import app
from absl import flags
from absl import logging

FLAGS = flags.FLAGS

flags.DEFINE_string('string_arg', None, 'sample string argument.')
flags.DEFINE_integer('integer_arg', 0, 'sample integer argument.')
flags.DEFINE_multi_integer('multi_integer_arg', [0, 1, 2], 'sample multi integer argument.')
flags.DEFINE_float('float_arg', 0.0, 'sample float argument.')
flags.DEFINE_multi_float('multi_float_arg', [0.0, 0.1, 0.2], 'sample multi float argument.')
flags.DEFINE_boolean('boolean_arg', False, 'sample boolean argument.')
flags.DEFINE_list('list_arg', [], 'sample list argument.')
flags.DEFINE_spaceseplist('spaceseplist_arg', [], 'sample spaceseplist argument.')

def main(unused_argv):
    del unused_argv

    logging.info(f'sample string argument is {FLAGS.string_arg}. type is {type(FLAGS.string_arg)}.')
    logging.info(f'sample integer argument is {FLAGS.integer_arg}. type is {type(FLAGS.integer_arg)}.')
    logging.info(f'sample multi integer argument is {FLAGS.multi_integer_arg}. type is {type(FLAGS.multi_integer_arg)}.')
    logging.info(f'sample float argument is {FLAGS.float_arg}. type is {type(FLAGS.float_arg)}.')
    logging.info(f'sample multi float argument is {FLAGS.multi_float_arg}. type is {type(FLAGS.multi_float_arg)}.')
    logging.info(f'sample boolean argument is {FLAGS.boolean_arg}. type is {type(FLAGS.boolean_arg)}.')
    logging.info(f'sample list argument is {FLAGS.list_arg}. type is {type(FLAGS.list_arg)}.')
    logging.info(f'sample spaceseplist argument is {FLAGS.spaceseplist_arg}. type is {type(FLAGS.spaceseplist_arg)}.')
    
if __name__ == '__main__':
    app.run(main)

実行時は下記のようになります。

DEFINE_multi_integer/floatは指定方法が他と少し異なるので注意してください。カンマ区切り指定等は出来なかったです。

$ python flags.py --string_arg test --integer_arg 100 --multi_integer_arg 1 --multi_integer_arg 2 --float_arg 1.2 --multi_float_arg 0.1 --multi_float_arg 0.2 --boolean_arg --list_arg  1,2,3 --spaceseplist_arg '1 2 3'
I0903 14:59:13.225122 140321057359680 flags.py:22] sample string argument is test. type is <class 'str'>.
I0903 14:59:13.225196 140321057359680 flags.py:23] sample integer argument is 100. type is <class 'int'>.
I0903 14:59:13.225226 140321057359680 flags.py:24] sample multi integer argument is [1, 2]. type is <class 'list'>.
I0903 14:59:13.225250 140321057359680 flags.py:25] sample float argument is 1.2. type is <class 'float'>.
I0903 14:59:13.225271 140321057359680 flags.py:26] sample multi float argument is [0.1, 0.2]. type is <class 'list'>.
I0903 14:59:13.225300 140321057359680 flags.py:27] sample boolean argument is True. type is <class 'bool'>.
I0903 14:59:13.225325 140321057359680 flags.py:28] sample list argument is ['1', '2', '3']. type is <class 'list'>.
I0903 14:59:13.225348 140321057359680 flags.py:29] sample spaceseplist argument is ['1', '2', '3']. type is <class 'list'>.

バリデーションも可能

lambdaを使用して引数のチェックを行うことも可能です。

値チェックをしたい場合は使用すると良さそうです。

また、各引数定義時に必須として設定は可能ですが、mark_flag_as_requiredを使用して後から必須設定することも可能です。

from absl import app
from absl import flags
from absl import logging

FLAGS = flags.FLAGS

flags.DEFINE_string('string_arg', None, 'sample string argument.')
flags.DEFINE_string('string_numeric_arg', '0', 'sample numeric string argument.')
flags.DEFINE_integer('integer_arg', 0, 'sample integer with validation argument.')
flags.DEFINE_integer('integer_required_arg', None, 'sample integer required argument.')

flags.register_validator('string_numeric_arg', lambda value: str.isnumeric(value), message='--string_numeric_arg is must be numeric.')
flags.register_validator('integer_arg', lambda value: value % 2 == 0, message='--integer_arg is must be divisible by 2.')
flags.mark_flag_as_required('integer_required_arg')
# register multiple arguments as required.
# flags.mark_flags_as_required(['integer_required_arg', ...])

def main(unused_argv):
    del unused_argv

    logging.info(f'sample string argument is {FLAGS.string_arg}.')
    logging.info(f'sample numeric string argument is {FLAGS.string_numeric_arg}.')
    logging.info(f'sample integer argument is {FLAGS.integer_arg}.')
    logging.info(f'sample integer required argument is {FLAGS.integer_required_arg}.')
    
if __name__ == '__main__':
    app.run(main)

実行時は下記のようになります。

値チェックでエラーとなった場合は指定したメッセージが表示されます。

$ python main.py --string_arg 'test' --string_numeric_arg '12' --integer_arg 10 --integer_required_arg 1
I0903 15:01:27.663400 140395343882048 validations.py:24] sample string argument is test.
I0903 15:01:27.663821 140395343882048 validations.py:25] sample numeric string argument is 12.
I0903 15:01:27.663985 140395343882048 validations.py:26] sample integer argument is 10.
I0903 15:01:27.664485 140395343882048 validations.py:27] sample integer required argument is 1.

$ python validations.py --string_arg 'test' --string_numeric_arg 'a' --integer_arg 10 --integer_required_arg 1
FATAL Flags parsing error: flag --string_numeric_arg=a: --string_numeric_arg is must be numeric.
Pass --helpshort or --helpfull to see help on flags.

-技術的な話
-, , ,