-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
argparse: positional arguments containing - in name not handled well #59330
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
To reproduce, try the following code: from argparse import ArgumentParser
a = ArgumentParser()
a.add_argument("foo-bar")
args = a.parse_args(["biz"])
print args, args.foo_bar Expected output:
Actual output: Namespace(foo-bar='biz')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Namespace' object has no attribute 'foo_bar' Other comments: I have not tested if this problem occurs in Python versions newer than 2.6. |
It does. |
Re-selecting Python 2.6 under version (I think it was accidentally unselected). Here as another related error: if I try to add dest="baz" to the a.add_argument() call, it throws ValueError: dest supplied twice for positional argument |
Nope, it was intentionally unselected. We use versions for the versions in which we will fix the bug, and 2.6 gets only security patches at this point. In any case, argparse isn't part of the stdlib in 2.6. |
Oh, and to make sure your second report doesn't get lost, could you open another issue for the other bug, and give a complete example? |
I'm working on this right now as part of EuroPython's CPython sprint. |
I don't see a valid use case to support "-" in the name of the positional argument. IMHO, it should raise an error (probably a ValueError) for the add_argument in this case ... Or we keep it as-is and close as wont-fix: if the op wants to pass "foo-bar" for the name of the positional argument ... it is his problem. In this case a single note in the documentation about using valid Python identifier for the names could be enough. |
I agree with Florent that this is maybe just a documentation issue, since the argument is accessible via getattr(). |
Florent, there are several reasons I think this is a valid use case. First, for optional arguments, '-' gets automatically replaced with '_' as the destination. I don't see any reason why optional and positional arguments should be treated differently when deciding the destination. This inconveniences the programmer who could naturally be inclined to follow some hyphenated optional arguments a.add_argument("--option-one")
a.add_argument("--option-two")
a.add_argument("--option-three") with hyphenated positional argument a.add_argument("positional-args") The programmer shouldn't have to pause and think about different naming requirements for optional and positional arguments. Second, persuading programmers to use underscores for positional args (eg, via proposed documentation patch) would have user-visible changes. Specifically, the automatically generated help/usage message would contain underscores instead of hyphens. Admittedly, this is fairly minor and inconsequential, but most programs use hyphens (not underscores) as delimiters in long options. I think it would be poor form to break user expectations in this regard. Last, I was apparently wrong (as Florent points out) that positional arguments whose names are invalid identifiers are impossible to retrieve. That's good to know, but that solution strikes me as just an ugly workaround. :/ |
Isn’t the obvious workaround to pass a dest argument which is a valid identifier? add_argument('spam-eggs', dest='spam_eggs') Maybe argparse in 3.4 could do this transformation automatically (see how namedtuple converts anything to valid identifiers). |
Eric: Specifically, it turns out that when the first argument to add_argument() starts with '-', it is interpreted as an optional argument and dest in _inferred_ from the first argument (eg, by stripping leading '-', and replacing remaining '-' with '_'). So supplying dest= to add_argument() overrides this heuristic. But when the first argument to add_argument() does not start with a '-', dest becomes exactly the first argument, and supplying dest as a keyword argument like you suggest yields the exception I reported earlier. Revisiting the documentation on dest (http://docs.python.org/dev/library/argparse.html#dest), this behavior is briefly and un-enlighteningly addressed in the first paragraph. The problem is that the paragraph suggests that dest can be explicitly set using keyword argument even for positional arguments; instead a ValueError is thrown as I have already mentioned. I suppose that patching argparse to replace '-' with '_' for positional arguments may break existing code for anyone that has figured out they can get hyphenated positional arguments with getattr(), which would make it a risky patch. But patching the module to allow explicitly setting dest via keyword argument shouldn't hurt anybody. For clarity, I recommend adding """(This conversion does NOT take place for positional arguments.)""" before the last sentence of paragraph 2 in the dest documentation. Since I had no idea about using getattr, I bet others wouldn't figure it out either. I suggest also adding a simple example in the documentation for anybody that wants to use an invalid Python identifier as an argument. (Please don't say this is an invalid use case because eg, ssh has options -1, -2, -4, and -6.) |
I'm sympathetic to the idea that '-' should be translated similarly for optional and positional arguments, but as you've noted, it would be a risky patch because it's already possible for people to use getattr on hyphenated arguments. So I think this isn't possible without a long deprecation process first.
I agree that it wouldn't hurt anybody. If you can find a way to do this, feel free to provide a patch. However, the correct way to have one name for the attribute (i.e. dest=) and one name for the help (i.e. metavar=) is to use the metavar argument like so: parser.add_argument('positional_args', metavar='positional-args') That said, this is not the first time I've seen someone run into this problem. I think the documentation could be improved in a few ways: (1) In the "name or flags" section, describe how the flags (for optional arguments) are translated into both a default "dest" (stripping initial '-' and converting '-' to '_') and into a default "metavar" (stripping initial '-' and uppercasing). Part of this is in the "dest" and "metavar" documentation, but probably belongs up in the "name or flags" documentation. Add cross-references to "dest" and "metavar" sections. (2) In the "name or flags" section, describe how the name (for positional arguments) are translated into the same default "dest" and "metavar" (no string munging at all). Again, move stuff from the "dest" and "metavar" sections as necessary. Add cross-references to "dest" and "metavar" sections. (3) In the "dest" section and somewhere in the "parse_args" section, describe how "getattr" can be used to get attributes whose names aren't valid Python identifiers. Maybe cross-reference this section from the edits in (2). |
I don't think that making
In the included patch.
If we make optional and positional arguments consistent, and provide |
Sorry, there was a small typo in the previous patch. Here's the newer version. |
Note that 15125-1.patch applies to Python 2.7 cleanly as it is a bugfix. |
15125-2.patch applies to the default branch. It makes dest behave the same for positional and optional arguments in terms of name mangling. Also, there is a backward-compatibility path in Namespace to support old-style getattr() access. However, it's not documented as we really don't want people to use it. |
Will apply the doc patches soon™ and wait for Steven’s feedback about the 3.4 behavior change. |
Any progress on this issue? Still persists in Python 3.4.1. |
It still needs a documentation patch. And if the documentation was clearer, would sfllaw's patch still be needed? |
Although it would be nice if the behavior were normalized between positional and optional args, it seems like doc patch would be the most straight-forward at this point. The down-side is that I suspect many people will assume the behavior for optional args, have it fail, and then consults the docs to see what happened. I would suggest making the note pretty obvious or early on in the docs for argparse. |
This issue is 7 years old and has 3 patches: it's far from being "newcomer friendly", I remove the "Easy" label. |
How about:
|
As this looks like it's not getting handled anytime soon, will the upstream accept at least a documentation update here https://docs.python.org/3/library/argparse.html#dest, explicitly mentioning that "positional arguments are not |
Yes, a doc PR would get reviewed. |
The PR is ready for review. |
There are issues with the proposed patches and #104092.
So, if we still want to implement name mangling for positional parameters, I suggest the following 3-step plan:
|
@sfllaw, is it your patches? Are you interesting in creating a PR based on 15125-1.patch? |
@serhiy-storchaka Sorry, but I no longer have good context for this patch. Please feel free to use it as a basis for your own PR, since you appear to have thought about this recently! |
Also improve the documentation. Specify how dest and metavar are derived from add_argument() positional arguments. Co-authored-by: Simon Law <[email protected]>
Created #125215 based on 15125-1.patch. Thank you for your contribution @sfllaw. |
Also improve the documentation. Specify how dest and metavar are derived from add_argument() positional arguments. Co-authored-by: Simon Law <[email protected]>
As a new user of argparse I've hit this issue and the experience was just bad and the workaround and documentation is far from optimal. Please reopen this issue!
Let's go through the new developer experience that already has some args: parser.add_argument('--input-file')
parser.add_argument('file')
args = parser.parse_args()
# Using args.input_file, args.file is fine Let's be more explicit about what 'file' is with
Quick investigation with Weird, but I there's this parser.add_argument('output-file', dest='output_file') which results in
Excuse me, what? That's not what "normally supplied" means. Or, after PR #125215 the error is: NO, I don't mean metavar. Then you read more and realize that you have to do this: parser.add_argument('output_file', metavar='output-file')
# OR
parser.add_argument(metavar='output-file', dest='output_file') Both versions are not intuitive and would be harder to automatically migrate in a future major release that fully fixes the issue. PROPOSALPlease fix |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
Linked PRs
The text was updated successfully, but these errors were encountered: