argparse default option based on another option

Question:

Suppose I have an argparse python script:

import argparse

parser = argparse.ArgumentParser()
    
parser.add_argument("--foo", required=True)

Now I want to add another option –bar, which would default to appending "_BAR" to whatever was specified by –foo argument.

My goal:

>>> parser.parse_args(['--foo', 'FOO'])
>>> Namespace(foo='FOO', bar="FOO_BAR")

AND

>>> parser.parse_args(['--foo', 'FOO', '--bar', 'BAR'])
>>> Namespace(foo='FOO', bar="BAR")

I need something like this:

parser.add_argument("--bar", default=get_optional_foo + "_BAR")
Asked By: paroxyzm

||

Answers:

I would, as a first try, get this working using an after-argparse function.

def addbar(args):
    if args.bar is None:
        args.bar = args.foo+'_BAR'

If this action needs to be reflected in the help, put it there yourself.

In theory you could write a custom Action for foo that would set the value of the bar value as well. But that requires more familiarity with the Action class.

I tried a custom Action that tweaks the default of the bar action, but that is tricky. parse_args uses the defaults right at the start, before it has acted on any of the arguments.

Answered By: hpaulj

Here’s another attempt at writing a custom Action class

import argparse
class FooAction(argparse.Action):
    # adapted from documentation
    def __call__(self, parser, namespace, values, option_string=None):
        setattr(namespace, self.dest, values)
        defaultbar = getattr(namespace, 'bar')
        try:
            defaultbar = defaultbar%values
        except TypeError:
            # BAR has already been replaced
            pass
        setattr(namespace, 'bar', defaultbar)

parser = argparse.ArgumentParser()
parser.add_argument("--foo", required=True, action=FooAction)
parser.add_argument("--bar", default="%s_BAR")

args = parser.parse_args(['--foo', 'Foo', '--bar', 'Bar'])
# Namespace(bar='Bar', foo='Foo')
args = parser.parse_args(['--foo', 'Foo'])
# Namespace(bar='Foo_BAR', foo='Foo')
args = parser.parse_args(['--bar', 'Bar', '--foo', 'Foo'])
# Namespace(bar='Bar', foo='Foo')

Note that the class has to know the dest of the --bar argument. Also I use a '%s_BAR' to readily distinguish between a default value, and a non default one. This handles the case where --bar appears before --foo.

Things that complicate this approach are:

  • default values are evaluated at add_argument time.
  • default values are placed in the Namespace at the start of parse_args.
  • flagged (optionals) arguments can occur in any order
  • the Action class is not designed to handle interacting arguments.
  • the bar action will not be called in the default case.
  • the bar default could be a function, but something would have to check after parse_args whether it needs to be evaluated or not.

While this custom Action does the trick, I still think the addbar function in my other answer is a cleaner solution.

Answered By: hpaulj
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.