简体   繁体   中英

argparse not handling abbreviations in subparser properly

(Run on python 3.6.0 )

TL;DR

Usage: prog.py {caesar | vigenere} [key]

parser = argparse.ArgumentParser()
subp = parser.add_subparsers()
caesar = subp.add_parser("caesar", aliases=["c"], allow_abbrev=True)
args = parser.parse_args()
$ python prog.py caes 123
prog.py: error: invalid choice: 'caes' (choose from 'caesar', 'c')

Why is the subparser abbreviation invalid even with allow_abbrev=True ?


LONG VER

Basically, having an issue getting argparse to accept abbreviated subparsers names/aliases.

Here's the code:

Usage: prog.py [caesar] [key]

import sys, argparse

def main(argv):
parser = argparse.ArgumentParser
         (description="runs text through X cipher")
subp = parser.add_subparsers\
         (help="sub-command help")

#<ArgumentParser object>
caesar = subp.add_parser\
         ("caesar", aliases=["c"], allow_abbrev=True)
caesar.add_argument\
         ("key", metavar = "key (any integer)",\
          type = int, default = 0)


args = parser.parse_args()
print(caesar)

if __name__ == "__main__":
sys.argv = list(str(c).lower() for c in sys.argv[0:])
main(sys.argv)

So from the code above, it should be expected that any of the following should be accepted:

- "Caesar" or "caesar"
- "C" or "c" 
- Any abbreviation in between "c" and "caesar" 

So here's the problem:

This works: $ python prog.py c 123 O

This gives an error: $ python prog.py caes 123 X

prog.py: error: invalid choice: 'cae' (choose from 'caesar', 'c')

Now here's the confusing part.

According to the argparse doc :

ArgumentParser supports the creation of such sub-commands with the add_subparsers() method. The add_subparsers() method is normally called with no arguments and returns a special action object. This object has a single method, add_parser() , which takes a command name and any ArgumentParser constructor arguments, and returns an ArgumentParser object that can be modified as usual.

  1. okay, so any object created with add_subparser() can create its own ArgumentParser object with object.add_parser() right?

  2. ...which means this newly created ArgumentParser object should be able to accept any ArgumentParser arguments yeah?

ArgumentParser definition:

class 
argparse.ArgumentParser(
prog=None, usage=None, 
description=None, epilog=None, 
parents=[],formatter_class=argparse.HelpFormatter, 
prefix_chars='-',fromfile_prefix_chars=None, 
argument_default=None,conflict_handler='error', 
add_help=True, allow_abbrev=True)

Create a new ArgumentParser object. All parameters should be passed as keyword arguments. Each parameter has its own more detailed description below, but in short they are:

allow_abbrev - Allows long options to be abbreviated if the abbreviation is unambiguous.

(default: True )

Changed in version 3.5: allow_abbrev parameter was added.

(this was on python 3.6.0)


Thanks in advance, guys

A patch to allow abbreviations of the subparser names was implemented, but then withdrawn when it proved to be buggy:

Issue 12713: allow abbreviation of sub commands by users

Allowing users to turn off abbreviations for long options is a different issue, handled in

Issue 14910: argparse: disable abbreviation

Two different parts of the code.

allow_abbrev - Allows long options to be abbreviated if the abbreviation is unambiguous.

A long option is created with:

caesar.add_argument('-f','--foobar')

With the default allow_abbrev value, this would work with '-f', '--foo', and '--foobar'. The long_option in this case is '--foobar'. With it False , '--foo' would not work.

It's the main parser that decides whether c or caesar or cae are valid subparser commands (via subp , the special action object created by parser.add_subparsers ). This behaves more like a positional with choices .

parser.add_argument('foo', choices = ['c', 'caesar'])

The error I'm getting is this:

usage: [-h] {caesar,c} ...
: error: unrecognized arguments: a e s

Hinting that abbreviations are supposed to be composable in the sense that two different abbreviations "c" and "a" can be referenced by passing ca .

What is it that should really happen there? ca is both a combination between the c and the (non-existing) a short form, as well as an abbreviation. Which should the parser prefer? Therefore this question had to be resolved explicitly when the library was designed: For predictability, you just cannot have both.

That being said, maybe you can tweak the result by passing conflict_handler='resolve' ? https://docs.python.org/3/library/argparse.html#allow-abbrev

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM