Search code examples
python-3.xargparse

How to set argparse arguments after parser.add_argument


I have a python function that that bulid argparse parser automatically for other python programs and need to create a argument dynamically with parser.add_argument but in some niche use cases will need check if the program wants to add a very specific option for this argument for example add the choices option to the argument so instead of checking with if, if the program wants to add option to the argument and create the argument with this option or not accordingly I want to create the argument with parser.add_argument and then set it if needed here is an example for I wish to do if there is a pythonic/argsparse way to do it: option 1

parser.add_argument('-e', '--example')
if add_choices:
    parser.set_argument('-e', choices=['example1', 'example2'])

this is the option that I try to avoid option 2

if add_choices:
    parser.add_argument('-e', '--example', choices=['example1', 'example2'])
else:
    parser.add_argument('-e', '--example')

is that possible? or must I stick to option 2? Thanks to everyone who answers!

I looked in argparse documations and almost expected that there will be a function that set a argument using it name (like the example '-e') but did not find anything.

in addition I searched in stack overflow and found a similar question that does not solve my problem argparse update choices of an argument because it suppose to edit the choices option (and after runnig the code its not even working as intended aka it does not change the choices for and argument) and not create it or any other option (for example it does not change the action, type and so on even though its doas touch my main issue).

(and obviously asked chatgpt but to no avail he keep leading me in circles with incorrect code).


Solution

  • I don't see what's wrong with option 2; if/else is perfectly good python construct.

    But if you insist you could do something like

    a = parser.add_argument('-e', '--example')
    if add_choices:
    a.choices = ['example1', 'example2']
    

    a is the Action object created by the add_argument. Do a print(a) to see a display of some of its attributes. You can, within limits, modify those attributes after creating the object.

    That action is also referenced in the parser._action list. In ordinary parser setup we don't hang on to the reference returned by add_argument, but when constructing the parser programmatically, or with utility functions, it may be convenient to keep your own list of Actions.

    But wait, I'm just repeating the answers in your link, including mine :)

    You might be able to use the first pattern if you change the conflict_handler

    https://docs.python.org/3/library/argparse.html#conflict-handler

    That pattern has conflicts with the '-e' flag. The default handler raises an error. The 'resolve' alternative would remove the '-e' from the first, leaving '--example', and make a new Action with '-e'.

    edit

    You talk about wanting to use the argument "name", like '-e'. But that's not really the name of the Action, it's just one of its option_strings.

    As I wrote above add_argument creates an Action instance:

    In [3]: a = parser.add_argument('-e','--example')
    

    Some of the attributes of that instance can be see with its repr display:

    In [4]: a
    Out[4]: _StoreAction(option_strings=['-e', '--example'], dest='example', nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)
    

    dest is the name that will be used when adding the value to the args namespace. That typically is deduced from the option strings, but can be set explicitly. And may be duplicated.

    All these other attributes can be changed after creation, as I showed with the choices.

    Note that option_strings contains the flag strings. For a positional that list will be empty. And trying add another -e argument will result in a conflict over that -e flag, when may be resolved.

    parser has a list of the Actions, _actions, and also dict pairing flags with their actions:

    In [5]: parser._option_string_actions
    Out[5]: 
    {'-h': _HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, required=False, help='show this help message and exit', metavar=None),
    
     '--help': _HelpAction(option_strings=['-h', '--help'], dest='help', nargs=0, const=None, default='==SUPPRESS==', type=None, choices=None, required=False, help='show this help message and exit', metavar=None),
    
     '-e': _StoreAction(option_strings=['-e', '--example'], dest='example', nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None),
    
     '--example': _StoreAction(option_strings=['-e', '--example'], dest='example', nargs=None, const=None, default=None, type=None, choices=None, required=False, help=None, metavar=None)}
    

    In this dict, the values are Actions (references). In this example there are only two, the default help, and the example. An action will appear in this dict once for each option_string. This dict is used during parsing to identify which Action will be used to handle each user provided flag. It's also used during the conflict handling. But there aren't any public methods that work with this dict.