-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
argparse: default args in mutually exclusive groups #63143
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
In argparse, default arguments have a strange behavior that shows up in mutually exclusive groups: specifying explicitly on the command-line an argument, but giving it its default value, is sometimes equivalent to not specifying the argument at all, and sometimes not. See the attached test diff: it contains two apparently equivalent pieces of code, but one passes and one fails. The difference is that, in CPython, int("42") is 42 but int("4200") is not 4200 (in the sense of the operator "is"). The line that uses "is" in this way is this line in argparse.py (line 1783 in 2.7 head): if argument_values is not action.default: |
The patch isn't a good unittest case because it produces an Error, not a Failure. It does, though, raise a valid question about how a Mutually_exclusive_group tests for the use of its arguments. As you note, argparse does use the This reworks your test case a bit: group = parser.add_mutually_exclusive_group()
group.add_argument('--foo', default='test')
group.add_argument('--bar', type=int, default=256)
group.add_argument('--baz', type=int, default=257) '--foo test --baz 257' will give the So which is right? Should it complain because 2 exclusive arguments are being used? Or should it be excused from complaining because the values match their defaults? The other issue is whether the values really match the defaults or not. With an Strings might have the same id or not, depending on how they are created. If I create So large integers (>256) behave like strings when used as defaults in this situation. It's the small integers that have unique ids, and hence don't trigger mutually_exclusive_group errors when they should. This mutually_exclusive_group 'is' test might not be ideal (free from all ambiguities), but I'm not sure it needs to be changed. Maybe there needs to be a warning in the docs about mutually_exclusive_groups and defaults other than None. |
A further complication on this. With the arguments I defined in the previous post p.parse_args('--foo test --baz 257'.split()) gives the mutually exclusive error message. p.parse_args(['--foo', 'test', '--baz', '257']) does not give an error, because here the 'test' argument string is the same as the default 'test'. So the m_x_g test thinks `--foo' is the default, and does not count as an input. Usually in testing an argparse setup I use the list and split arguments interchangeably, but this shows they are not equivalent. |
Getting consistently one behavior or the other would be much better imho; I think it's wrong-ish to have the behavior depend uncontrollably on implementation details. But I agree that it's slightly messy to declare which of the two possible fixes is the "right" one. I'm slightly in favor of the more permissive solution ("--bar 42" equivalent to no arguments at all if 42 is the default) only because the other solution might break someone's existing code. If I had no such backward-compatibility issue in mind, I'd vote for the other solution (you can't specify "--bar" with any value, because you already specified "--foo"). |
Please let's not be pedantic about what a "good unittest" is. |
Changing the test from if argument_values is not action.default: to
makes the behavior more consistent. Strings and large ints behave like small ints, matching the default and not counting as "present" Simply using |
A possibly unintended consequence to this p=argparse.ArgumentParser()
g=p.add_mutually_exclusive_group(required=True)
g.add_argument('--foo',default='test')
g.add_argument('--bar',type=int,default=42)
p.parse_args('--bar 42'.split()) raises an In the original code p.parse_args('--foo test'.split()) does not raise an error because 'test' does not qualify as default. But with the change I proposed, it does raise the error. This issue may require adding a Note that this contrasts with the handling of ordinarily required arguments. p.add_argument('--baz',type=int,default=42,required=True) '--baz 42' does not raise an error. It is 'present' regardless of whether its value matches the default or not. This argues against tightening the |
I should add that defaults with required arguments (or groups?) doesn't make much sense. Still there's nothing in the code that prevents it. |
Fwiw I agree with you :-) I'm just relaying a bug report that originates on PyPy (https://bugs.pypy.org/issue1595). |
This test_argparse.TestMutuallyExclusiveOptionalAndPositional is such a case. If this is the case, what we need is a more reliable way of knowing whether We could rewrite the
is a little better, but still feels like a kludge. Having |
At the very least the Possible variations on how |
This patch uses a narrow criteria - if In effect, the only change from previous behavior is that small ints (<257) now behave like large ints, strings and other objects. It removes the nonstandard 'is not action.default' test, and should behave consistently across all platforms (including pypy). |
The patch looks good to me. It may break existing code, though, as reported on https://bugs.pypy.org/issue1595. I would say that it should only go to trunk. We can always fix PyPy (at Python 2.7) in a custom manner, in a "bug-to-bug" compatibility mode. |
This patch corrects the handling of if parser.add_argument('badger', type=int, nargs='?', default=2) # or '2' and the original test 'seen_non_default_actions' is:
'argument_values' will be an 'int' regardless of the default. But it will pass the 'is' test with the (small) int default but not the string default. With the patch proposed here, both defaults behave the same - 'badger' will not appear in 'seen_non_default_actions' if it did not occur in the argument_strings (i.e. match an empty string). I may add case like this to |
I need to tweak the last patch so 'using_default' is also set when an "nargs='*'" positional is set to the '[]' default. if action.default is not None:
value = action.default
+ using_default = True
else:
value = arg_strings
+ using_default = True # tweak |
This came up again, http://bugs.python.org/issue30163 An optional with int type and small integer default. |
paul, will you work on this patch? or I can help this issue, too. |
I haven't downloaded the development distribution to this computer, so can't write formal patches at this time. |
Another manifestation of the complications in handling '?' positionals is in http://bugs.python.org/issue28734 argparse: successive parsing wipes out nargs=? values |
Any progress with this? I believe it would fix my use case:
Outputs (using 3.6.3):
Are there know workarounds for this? |
Did you copy the output right? Testing your parser: Without any arguments, I get the exclusive group error - the group is required: 0930:~/mypy/argdev$ python3 bpo-18943.py 0931:~/mypy/argdev$ python3 --version With one flag but not its argument, I get the error that you display. That has nothing to do with the grouping. 0932:~/mypy/argdev$ python3 bpo-18943.py --ptz-get-status |
On 2017-12-06 19:43, paul j3 wrote:
In my example I pasted, I had hardcoded arguments:
|
That's not how flagged (optionals) arguments work. The default value is used if the flag is not provided at all. One of your arguments is a 'store_true'. Its default value if False, which is changed to True if the '--device-get-capabilities' flag is provided. "nargs='?'" provides a third option, assigning the 'const' value if the flag is used without an argument. In any case your problem isn't with a required mutually exclusive group (defaults or not). It has to do with understanding optionals and their defaults. |
On 2017-12-06 20:28, paul j3 wrote:
This did a "click" in my head. It works now with I am really sorry for the noise, due to misunderstanding while reading (skipping-throuhg?) Python documentation. |
Arguments with the value identical to the default value (e.g. booleans, small integers, empty or 1-character strings) are no longer considered "not present".
…4307) Arguments with the value identical to the default value (e.g. booleans, small integers, empty or 1-character strings) are no longer considered "not present".
…ythonGH-124307) Arguments with the value identical to the default value (e.g. booleans, small integers, empty or 1-character strings) are no longer considered "not present". (cherry picked from commit 3094cd1) Co-authored-by: Serhiy Storchaka <[email protected]>
…ythonGH-124307) Arguments with the value identical to the default value (e.g. booleans, small integers, empty or 1-character strings) are no longer considered "not present". (cherry picked from commit 3094cd1) Co-authored-by: Serhiy Storchaka <[email protected]>
…GH-124307) (GH-124419) Arguments with the value identical to the default value (e.g. booleans, small integers, empty or 1-character strings) are no longer considered "not present". (cherry picked from commit 3094cd1) Co-authored-by: Serhiy Storchaka <[email protected]>
…GH-124307) (GH-124418) Arguments with the value identical to the default value (e.g. booleans, small integers, empty or 1-character strings) are no longer considered "not present". (cherry picked from commit 3094cd1) Co-authored-by: Serhiy Storchaka <[email protected]>
It has taken lewis and I a combined nearly 24 hours of debugging to discover that there is a breaking change in argparse between python 3.12.6 and python 3.12.7 (python/cpython#63143) This was causing https://git.ligo.org/lscsoft/bilby_pipe/-/issues/310 which meant that any time we attempted to parse bilbyjob ini files the server would crash
It has taken lewis and I a combined nearly 24 hours of debugging to discover that there is a breaking change in argparse between python 3.12.6 and python 3.12.7 (python/cpython#63143) This was causing https://git.ligo.org/lscsoft/bilby_pipe/-/issues/310 which meant that any time we attempted to parse bilbyjob ini files the server would crash
It has taken lewis and I a combined nearly 24 hours of debugging to discover that there is a breaking change in argparse between python 3.12.6 and python 3.12.7 (python/cpython#63143) This was causing https://git.ligo.org/lscsoft/bilby_pipe/-/issues/310 which meant that any time we attempted to parse bilbyjob ini files the server would crash Approved-by: Lewis Lakerink <[email protected]> Merged by: Owen Cole <[email protected]> See merge request https://gitlab.com/CAS-eResearch/GWDC/gwcloud_bilby/-/merge_requests/142
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: