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?

+3


source to share


2 answers


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.

+5


source


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

.

0


source







All Articles