Cause Python's argparse to execute action for default

前端 未结 2 2065
旧时难觅i
旧时难觅i 2021-02-19 06:50

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 possi

相关标签:
2条回答
  • 2021-02-19 07:31

    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.

    0 讨论(0)
  • 2021-02-19 07:33

    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 ?

    0 讨论(0)
提交回复
热议问题