70

Is it possible to check argparse choices in case-insensitive manner?

import argparse
choices = ["win64", "win32"]
parser = argparse.ArgumentParser()
parser.add_argument("-p", choices=choices)
print(parser.parse_args(["-p", "Win32"]))

results in:

usage: choices.py [-h] [-p {win64,win32}]
choices.py: error: argument -p: invalid choice: 'Win32' (choose from 'win64','win32')
Peter
  • 8,517
  • 4
  • 37
  • 63

3 Answers3

107

Transform the argument into lowercase by using

type = str.lower

for the -p switch.

This solution was pointed out by chepner in a comment. The solution I proposed earlier was

type = lambda s : s.lower()

which is also valid, but it's simpler to just use str.lower.

Jean-François Corbett
  • 34,562
  • 26
  • 126
  • 176
5gon12eder
  • 21,864
  • 5
  • 40
  • 85
  • 28
    You can get rid of the lambda by simply using `type = str.lower`, since `str.lower` is a function that takes a string and returns it in lower case. – chepner Dec 23 '14 at 22:02
  • 3
    Since the [string.lower(s)](https://docs.python.org/2/library/string.html#string.lower) _function_ was deprecated and replaced with the [s.lower()](https://docs.python.org/2/library/stdtypes.html#str.lower) _method_, I would recommend the lambda approach as originally suggested. – Mark Mar 14 '17 at 03:01
  • 7
    @Mark Neither version uses the deprecated function from the `string` module. Note that `str.lower` (without `ing`) is used, which is the method from the `str` class taking `self` as its only parameter which is why the trick works. – 5gon12eder Mar 16 '17 at 01:47
  • @5gon12eder Thanks for the heads up, that makes sense! Good to know. @chepner's description threw me off because he said that this is the str.lower _function_ which "takes a string" which would imply he was referring to the now deprecated _function_ rather than the replacing _method_ that doesn't take any input, but acts on `self`. – Mark Mar 17 '17 at 06:14
  • `obj.method(*args, **kwargs)` is for the most part identical to `type(obj).method(obj, *args, **kwargs)`. `str.lower` should rightly be called a function because none of its arguments are bound, whereas `obj.lower` is a method because `obj` is bound as the first argument. – BallpointBen Sep 05 '18 at 15:11
18

Using lower in the type is nice way of doing this, if you don't mind loosing the case information.

If you want to retain the case, you could define a custom choices class. The choices needs two methods, __contains__ (for testing in), and iteration (to list the choices).

class mylist(list):
    # list subclass that uses lower() when testing for 'in'
    def __contains__(self, other):
        return super(mylist,self).__contains__(other.lower())
choices=mylist(['win64','win32'])
parser = argparse.ArgumentParser()
parser.add_argument("-p", choices=choices)
print(parser.parse_args(["-p", "Win32"]))
# Namespace(p='Win32')

The help is:

usage: ipython [-h] [-p {win64,win32}]

optional arguments:
  -h, --help        show this help message and exit
  -p {win64,win32}
hpaulj
  • 175,871
  • 13
  • 170
  • 282
  • Just to be safe, I'd probably do `choices = mylist(map(str.lower, ['win64', 'win32']))`, or override `__init__` to do this. – BallpointBen Sep 05 '18 at 15:19
2

Keeping the case information would also be possible with a one liner:

type = lambda arg: {x.lower(): x for x in choices}[arg.lower()],

Where choices would be the same list as passed to the choices parameter.

Andreas Walter
  • 676
  • 9
  • 23