Python argparse check choices before type
Question: I'm trying to enable a user to pass in a function name. For some reason it
seems that argparse performs the type check/conversion BEFORE it checks the
choices. Is this a bug? Best thing to do?

    
    
    import argparse
    
    def foo():
      return 'foo'
    
    def bar():
      return 'bar'
    
    parser = argparse.ArgumentParser()
    functions = {f.__name__:f for f in [foo, bar]}
    parser.add_argument("function", type=lambda f: functions.get(f), help="which function", choices=functions)
    args = parser.parse_args()
    print(args.function())
    

This throws:

    
    
    $ python blah.py foo
    usage: blah.py [-h] {foo,bar}
    blah.py: error: argument function: invalid choice: <function foo at 0x7f65746dd848> (choose from 'foo', 'bar')
    


Answer: Yes, during parsing the `type` then `choices` order is clear and intentional
(and not just incidental). When preparing to assign `arg_strings` to the
`namespace` it calls `_get_values`, which does:

    
    
      def _get_values(self, action, arg_strings)
            .... (various nargs tests)
            value = self._get_value(action, arg_string)
            self._check_value(action, value)
            return value
    

where `_get_value` applies the `action.type` function, and `_check_value`
tests

    
    
    value not in action.choices  
    

For parsing `choices` only has to respond to the `in` (`__contains__`)
expression.

So `choices` have to reflect values after conversion. If `type` is `int`, then
`choices=[1,2,3]` is correct, `['1','2','3']` is not.

There are some (largely unresolved) bug issues over the display of the
choices. Long lists, e.g. `range(100)` work in parsing, but don't display
nicely. And display also requires that `choices` be iterable (e.g. a list,
tuple, dictionary). This display issue affects the usage, the help and the
error messages (each formats `choices` slightly differently).

`metavar` is your most powerful tool for replacing an undesirable `choices`
list. I'd have to run a test case to see whether it solves things for all 3
situations.

