Python argparse choices of a list of strings take a unique partial list item
I would like to have the following rule
parser.add_argument('move', choices=['rock', 'paper', 'scissors'])
also work if you pass in a unique subset of characters (for example, "k" or "oc" would be accepted as "rock", but not "r" because it is not unique).
I need to be able to run a script with one or more arguments in the fastest way possible to avoid writing the entire argument name when a subset would be enough for the script to understand the choice.
Is there a way to get this result while still using a handy list that automatically integrates into help and error handling?
source to share
You can define a custom subclass list
that maintains your operator definition in
as "contains (part of) an element exactly once" like this:
class argList(list):
def __contains__(self, other):
"Check if <other> is a substring of exactly one element of <self>"
num = 0
for item in self:
if other in item:
num += 1
if num > 1:
return False
return num==1
def find(self, other):
"Return the first element of <self> in which <other> can be found"
for item in self:
if other in item:
return item
Then you can use it to create a list of arguments:
>>> l = argList(["rock", "paper", "scissors"])
>>> "rock" in l
True
>>> "ck" in l
True
>>> "r" in l
False
>>> "q" in l
False
and use it to create your parser:
>>> import argparse
>>> parser = argparse.ArgumentParser()
>>> l = argList(["rock", "paper", "scissors"])
>>> parser.add_argument("move", choices=l)
_StoreAction(option_strings=[], dest='move', nargs=None, const=None, default=Non
e, type=None, choices=['rock', 'paper', 'scissors'], help=None, metavar=None)
It now handles the arguments correctly (although the error message is still a little misleading):
>>> parser.parse_args(["rock"])
Namespace(move='rock')
>>> parser.parse_args(["r"])
usage: [-h] {rock,paper,scissors}
: error: argument move: invalid choice: 'r' (choose from 'rock', 'paper', 'scissors')
Note that the argument entered is retained, which (of course) may be incomplete:
>>> parser.parse_args(["ck"])
Namespace(move='ck')
so you will need to figure out which of your actual arguments was chosen:
>>> args = parser.parse_args(["ck"])
>>> l.find(vars(args)["move"])
'rock'
I haven't tested this much further - it's possible that overriding in `has unexpected side effects in argument lists, and I doubt that this behavior violates the principle of least surprise - but that's a start.
source to share
The type solution works very well, but if you don't want to go to all the trouble with the subclass list
, you can take an argument type
and define your own function like:
choices = ['rock','paper','scissors']
def substring(s):
options = [c for c in choices if s in c]
if len(options) == 1:
return options[0]
return s
parser = argparse.ArgumentParser()
parser.add_argument("move", choices = choices, type = substring)
When you pass an argument to move, the argument string will be passed to your subscript function. Then, if the argument string is a substring of exactly one of the strings that you specified in the list choices
, it will return that selection. Otherwise it will return your original argument string, and argparse will probably fail afterwards invalid choice
.
source to share