Pergunta

I am using argparse's action to add various data to a class. I would like to use that action on the default value if that arg is not provided at the command line. Is this possible? Thanks!

Foi útil?

Solução

argparse does not use the action when applying the default. It just uses setattr. It may use the type if the default is a string. But you can invoke the action directly.

Here I use a custom action class borrowed from the documentation. In the first parse_args nothing happens. Then I create a new namespace, and invoke the action on the default. Then I pass that namespace to parse_args. To understand this, you many need to import it into an interactive shell, and examine the attributes of the namespace and action.

# sample custom action from docs
class FooAction(argparse.Action):
    def __call__(self, parser, namespace, values, option_string=None):
        print('Setting: %r %r %r' % (namespace, values, option_string))
        setattr(namespace, self.dest, 'action:'+values)
p = argparse.ArgumentParser()
a1 = p.add_argument('--foo', action=FooAction, default='default')
print 'action:',a1
print p.parse_args([])

ns = argparse.Namespace()
a1(p, ns, a1.default, 'no string') # call action
print p.parse_args([],ns)
print p.parse_args(['--foo','1'],ns)

which produces:

action: FooAction(option_strings=['--foo'], dest='foo', nargs=None, const=None, default='default', type=None, choices=None, help=None, metavar=None)
Namespace(foo='default')
Setting: Namespace() 'default' 'no string'
Namespace(foo='action:default')
Setting: Namespace(foo='action:default') '1' '--foo'
Namespace(foo='action:1')

I tailored the output to highlight when the action is being used.


Here's a way of performing a special action on an argument that isn't given on the command line (or given with a value == to the default). It's a simplification of the class given in https://stackoverflow.com/a/24638908/901925.

class Parser1:
    def __init__(self, desc):
        self.parser = argparse.ArgumentParser(description=desc)
        self.actions = []

    def milestone(self, help_='milestone for latest release.', default=None):
        action = self.parser.add_argument('-m', '--milestone', help=help_, default=default)
        self.actions.append(action)
        return self

    def parse(self):
        args = self.parser.parse_args()
        for a in self.actions:
            if getattr(args, a.dest) == a.default:
                print 'Please specify', a.dest
                values = raw_input('>')
                setattr(args, a.dest, values)
        return args

print Parser1('desc').milestone(default='PROMPT').parse()

The prompting is done after parse_args. I don't see any reason to call parse_args again.

Outras dicas

I needed to prompt the user if an option was not specified - that's how I did it:

class _PromptUserAction(argparse.Action):

    def __call__(self, parser, namespace, values, option_string=None):
        if values == self.default:
            print 'Please specify', self.dest
            values = raw_input('>')
        setattr(namespace, self.dest, values)

class Parser:
    def __init__(self, desc, add_h=True):
        self.parser = argparse.ArgumentParser(description=desc, add_help=add_h,
                        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
        #actions to be run on options if not specified (using default to check)
        self.actions = []

    @staticmethod
    def new(description, add_help=True):
        return Parser(description, add_help)

    # ...

    def milestone(self, help_='Specify the milestone for latest release.'):
        action = self.parser.add_argument('-m', '--milestone',
                                          dest='milestone',
                                          action=_PromptUserAction,
                                          default='PROMPT', # needed I think
                                          type=str,
                                          help=help_)
        self.actions.append(action)
        return self

    def parse(self):
        """
        Return an object which can be used to get the arguments as in:
            parser_instance.parse().milestone

        :return: ArgumentParser
        """
        args = self.parser.parse_args()
        # see: http://stackoverflow.com/a/21588198/281545
        dic = vars(args)
        ns = argparse.Namespace()
        for a in self.actions:
            if dic[a.dest] == a.default:
                a(self.parser, ns, a.default) # call action
        # duh - can I avoid it ?
        import sys
        return self.parser.parse_args(sys.argv[1:],ns)

I am interested if this can somehow be done without having to reparse the args (the import sys part). Maybe some constructor options for argparse.Action ?

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top