Python argparse with optional positional and default None

Question:

Working with Python and argparse, trying to accomplish the following:

$> my_app
Namespace(positional=None)
$> my_app file.txt somedir
Namespace(positional=['file.txt', 'somedir'])

i.e., a positional argument of type list, whose default is None. I would expect the following code to accomplish this:

p = argparse.ArgumentParser()
p.add_argument("positional", nargs='*', default=None)

print(p.parse_args())

But I get:

$> my_app
Namespace(positional=[])
$> my_app file.txt somedir
Namespace(positional=['file.txt', 'somedir'])

The rest of my code uses None as defaults for lists. If None is provided, the code select a sensible default. Thus passing [] is not an option.

The behavior of argparse does rather feel like a bug, but maybe I’m missing something. Any thoughts?


Answer (thx hpaulj) seems to be "as intended" to which I would add "completely unintuitively".

Asked By: Sokrutu

||

Answers:

Your case is handled in

def _get_values(self, action, arg_strings):
    ...
    # when nargs='*' on a positional, if there were no command-line
    # args, use the default if it is anything other than None
    elif (not arg_strings and action.nargs == ZERO_OR_MORE and
          not action.option_strings):
        if action.default is not None:
            value = action.default
            self._check_value(action, value)
        else:
            # since arg_strings is always [] at this point
            # there is no need to use self._check_value(action, value)
            value = arg_strings

Normally the default is placed in the Namespace at the start of parsing. During parsing that default is overwritten with value(s) provided by the user. optionals are ‘seen’ when the right flag is used. positionals are ‘seen’ when the right number of strings are present. Since ‘*’ and ‘?’ accept 0 strings, they will always be ‘seen’. Thus their defaults require special handling, if at all.

Answered By: hpaulj

You can achieve the desired behavior like this

import argparse

p = argparse.ArgumentParser()
p.add_argument("positional", nargs='*', default=argparse.SUPPRESS)

print(p.parse_args(namespace=argparse.Namespace(positional=None)))

This prevents the arguments from appearing at all in the namespace which on turn causes the default namespace to take over.

Answered By: Wombatz
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.