ABSEIL - alternative of argparse

When writing Python programs, which package do you use for argument parsing?

I beleive most people are using argparse.

In fact, using abseil (absl-py; released from Google) package, argument parsing become much easier and convenient than argparse. In this article I will show you how to use abseil.

f:id:xterm256color:20180728084141j:plain

Contents

How to install

pip install absl-py

Example

Seeing is believing. So I will show abseil 's sample code.

Code

# sample.py
from absl import app
from absl import flags

FLAGS = flags.FLAGS

flags.DEFINE_string(
    'foo', 'default value', 'help message of this argument.')


def main(argv):
  print('FLAGS.foo is {}'.format(FLAGS.foo))


if __name__ == '__main__':
  app.run(main)

Result

For example, pass the parameter like below.

$ python sample.py --foo bar
FLAGS.foo is bar

And, You can display a help message with sample.py --help or sample.py -?

$ python sample.py --help

       USAGE: sample.py [flags]
flags:

sample.py:
  --foo: help message of this argument.
    (default: 'default value')

Try --helpfull to get a list of all flags.

Explanation

I will briefly explain the sample code above.

1. Import absl.app, abls.flags and create a FLAGS instance

from absl import app
from absl import flags

FLAGS = flags.FLAGS

2. Define the flags. This time we are defining flags to receive in string type

flags.DEFINE_string(
  'foo', 'default value', 'help message of this argument.')

3. Call the main function by app.run(main)

if __name__ == '__main__':
  app.run(main)

4. You can get the parameter with FLAGS.xxx

print('FLAGS.foo is {}'.format(FLAGS.foo))

Pretty easy, right?

Reasons to use abseil

There are three reasons why I use abseil instead of argparse.

It can be written simpler than argparse. I do not have to write parser = argparse.ArgumentParser() and parser.parse_args().

Help messages are more useful than argparse As you can see the results of the sample execution above, you can see the default value of the flag is also displayed properly in the help message. Since argparse does not show default value.

Arguments of bool type, list type, enum type can be easyly set When you use bool type parameters with argparse, you have to write something like below code.

parser.add_argument('--feature', dest='feature', action='store_true')
parser.add_argument('--no-feature', dest='feature', action='store_false')
parser.set_defaults(feature=True)

Since there is flags.DEFINE_bool() function in abseil, and you can easily set boolean flag.

For example, if you write below

flags.DEFINE_bool('bar', True, 'some message...')

The arguments --bar and --nobar are set automatically. If you append --nobar option as run-time argument, FLAGS.bar will return False.

Other functions such as flags.DEFINE_list() and flags.DEFINE_emum() are also provided, and both are useful.

More details

Functions to define flags

# str, int, float, bool type
flags.DEFINE_string('s', 'default value', 'help message of this argument.')
flags.DEFINE_integer('i', 1, 'help message of this argument.')
flags.DEFINE_float('f', 0.1, 'help message of this argument.')
flags.DEFINE_bool('b', True, 'help message of this argument.')

# List type
# When you give `--hoge x,y`, FLAGS.hoge will be set as ['x', 'y'].
flags.DEFINE_list(
    'hoge', None, 'help message of this argument.')

# Enum type.
# In the following cases, if you give a value other than a, b, c you get an error.
flags.DEFINE_enum(
    'restricted', 'a', ['a', 'b', 'c'], 'help message of this argument.')

# alias. --bar is considered as alias of --foo.
flags.DEFINE_bool('foo', True, 'help message of this argument.')
flags.DEFINE_alias('bar', 'foo')

Get dictionary of flag values.

FLAGS.flag_values_dict()

Set short name of flag.

# You can use -f and --fuge to set FLAGS.fuga.
flags.DEFINE_list(
    'fuga', None, 'help message of this argument.', short_name='f')

Overwrite flag value.

FLAGS.foo = 'a'

Required flag

if __name__ == '__main__':
  flags.mark_flags_as_required(['hoge', 'fuga'])
  app.run(main)

Set flag values from text file

# If you give `--flagfile /path/to/param.txt` at runtime, arguments are read from param.txt.
# When command line argument is given, it overrides the value written in param.txt.  
$ python sample.py --flagfile /path/to/param.txt

In param.txt, write arguments as follows.

--hoge=a
--fuga=1

Frequently caused error

Case 1

Traceback (most recent call last):
  File "./sample.py", line 43, in <module>
    app.run(main)
  File "/home/n/anaconda2/envs/absl/lib/python2.7/site-packages/absl/app.py", line 274, in run
    _run_main(main, args)
  File "/home/n/anaconda2/envs/absl/lib/python2.7/site-packages/absl/app.py", line 238, in _run_main
    sys.exit(main(argv))
TypeError: main() takes no arguments (1 given)

How to fix : Replace def main(): with def main(argv):

Case 2

WARNING: Logging before flag parsing goes to stderr.
E0606 00:00:59.346868 139914210911616 _flagvalues.py:487] Trying to access flag --s before flags were parsed.
Traceback (most recent call last):
  File "./sample.py", line 44, in <module>
    main(sys.argv[1:])
  File "./sample.py", line 27, in main
    print('FLAGS.s is {}'.format(FLAGS.s))
  File "/home/n/anaconda2/envs/absl/lib/python2.7/site-packages/absl/flags/_flagvalues.py", line 488, in __getattr__
    raise _exceptions.UnparsedFlagAccessError(error_message)
absl.flags._exceptions.UnparsedFlagAccessError: Trying to access flag --s before flags were parsed.

How to fix : Did you call main function directly? Call Alternately call app.run(main).