Issue 9625: argparse: Problem with defaults for variable nargs when using choices (original) (raw)

Issue9625

process

Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: Jan Hutař, berker.peksag, bethard, ced, deleted250130, eric.araujo, eric.smith, jameshcorbett, lanzz, macfreek, paul.j3, regis, rhettinger, sebix, thesociable, zumoshi
Priority: normal Keywords: patch

Created on 2010-08-17 09:19 by thesociable, last changed 2022-04-11 14:57 by admin.

Files
File name Uploaded Description Edit
issue9625.diff bethard,2012-07-21 22:19 review
issue9625.patch ced,2012-11-03 16:00 review
issue9625_1.patch paul.j3,2013-06-27 06:33 review
issue9625_2.patch paul.j3,2013-07-03 21:36 review
notes.txt paul.j3,2014-05-02 02:04
Messages (17)
msg114108 - (view) Author: Martin Pengelly-Phillips (thesociable) Date: 2010-08-17 09:19
Variable argument count plays badly with choices. Example: ======== >>> import argparse >>> parser = argparse.ArgumentParser() >>> parser.add_argument('choices', nargs='*', default='a', choices=['a', 'b', 'c']) >>> args = parser.parse_args() >>> print type(args.choices) <type 'str'> >>> args = parser.parse_args(['a']) >>> print type(args.choices) <type 'list'> If the user specifies the value on the command line then a list is used, but if the value comes from the default a string is used. Unfortunately, changing default to a list value gives an error: error: argument choices: invalid choice: ['a'] (choose from 'a', 'b', 'c') Additionally, this means it is also not possible to use default=['a', 'c']. The current workaround is to create a custom type: def my_type(string): if string not in ['a', 'b', 'c']: raise TypeError return string
msg151780 - (view) Author: Michał M. (regis) Date: 2012-01-22 17:55
Maybe it will sound strange, but what is this task REALLY about? I mean - I can see two problems here, but no clear information about which problem is a real problem and - if it is - what is the expected behavior. Problems I can see are: 1) Type of returned value changes depending on the value source (list for user provided or list for default) 2) It's impossible to set list as 'default' I understand that this task concentrates on 1st one, however changing behavior described in 2nd would - in my oppinion - fix 1st one too - am I right? User would "define" the returned type by defining the 'default' as a list or string. But - if we assume that we only discuss 1st problem here - what is the expected behavior? Provided workaround is suggesting that we expect string, but - basing on current nargs behavior and "intuition" - I'd rather expect to get a list, when I define nargs with "*" or "+". This sounds "natural" for me. Could someone explain me the problem and the expected behavior in a clear way? Sorry if I'm not clear, but it's my first post here and maybe I've missed something in the description or I assume something that is not true (especially that the task is quite old... ;) ).
msg151807 - (view) Author: Michał M. (regis) Date: 2012-01-23 10:50
Of course I've made a mistake: "list for user provided or list for default" should be: "list for user provided or STRING for default"
msg151857 - (view) Author: Martin Pengelly-Phillips (thesociable) Date: 2012-01-23 22:23
The real issue is that the choices flag does not work with a default flag and * nargs. The following works as expected: >>> parser.add_argument('chosen', nargs='*', default=['a']) >>> print(parser.parse_args()) Namespace(chosen=['a']) >>> print(parser.parse_args(['a', 'b'])) Namespace(chosen=['a', 'b']) Introducing a choices constraint breaks down when using the defaults: >>> parser.add_argument('chosen', nargs='*', default=['a'], choices=['a', 'b']) >>> print(parser.parse_args(['a'])) Namespace(chosen=['a']) >>> print(parser.parse_args()) error: argument chosen: invalid choice: ['a'] (choose from 'a', 'b') I would expect instead to have Namespace.chosen populated with the default list as before, but the choices constraint check does not validate correctly. I think that changing the choices constraint logic to iterate over the default values if nargs results in a list would be a possible solution.
msg166086 - (view) Author: Steven Bethard (bethard) * (Python committer) Date: 2012-07-21 22:19
I agree that this looks like a bug. I think the fix is something like the attached patch, but it needs some tests to make sure that it fixes your problem.
msg174642 - (view) Author: Cédric Krier (ced) * Date: 2012-11-03 16:00
Here is a new version of the patch with tests
msg191224 - (view) Author: Cédric Krier (ced) * Date: 2013-06-15 19:02
ping
msg191932 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2013-06-27 06:33
I've added 2 more tests, one with default='c', which worked before. one with default=['a','b'], which only works with this change. http://bugs.python.org/issue16878 is useful reference, since it documents the differences between nargs="?" and nargs="*", and their handling of their defaults.
msg192258 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2013-07-03 21:36
Change "choices='abc'" to "choices=['a', 'b', 'c']", as discussed in issue 16977 (use of string choices is a bad example)
msg192716 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2013-07-09 03:07
The patch I just posted to http://bugs.python.org/issue16468 uses this fix.
msg217677 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2014-05-01 04:46
There's a complicating issue - should these default values be passed through the type function? In most cases in `_get_values`, the string first goes through `_get_value`, and then to `_check_value`. For example the 'else:' case: value = [self._get_value(action, v) for v in arg_strings] for v in value: self._check_value(action, v) The '*' positional case could coded the same way, allowing: parser.add_argument('foo', nargs='*', type=int, choices=range(5), default=['0',1,'2']) and objecting to default=[6,'7','a'] # out of range string or int or invalid value This does impose a further constraint on the 'type' function, that it accepts a converted value. e.g. int(1) is as valid as int('1'). But we need to be careful that this case is handled in a way that is consistent with other defaults (including the recent change that delayed evaluating defaults till the end).
msg285865 - (view) Author: paul j3 (paul.j3) * (Python triager) Date: 2017-01-20 01:28
Recent StackOverFlow question related to this issue http://stackoverflow.com/questions/41750896/python-argparse-type-inconsistencies-when-combining-choices-nargs-and-def/41751730#41751730
msg347742 - (view) Author: João Eiras (João Eiras) Date: 2019-07-12 12:55
Another workaround for who might ever need it. The benefit of this solution comparing to a custom type is that argparse will generate the help string properly with the choices, and this solution does workaround the place when the bug happens: class Choices(tuple): # Python bug https://bugs.python.org/issue27227 def __new__(cls, *args, **kwargs): x = tuple.__new__(cls, *args, **kwargs) Choices.__init__(x, *args, **kwargs) return x def __init__(self, *args, **kwargs): self.default = [] def __contains__(self, item): return tuple.__contains__(self, item) or item is self.default choices = Choices(("value1", "value2", "value3", ...)) parser.add_argument( ..., nargs="*", choices=choices, default=choices.default, ...)
msg356859 - (view) Author: Mihail Milushev (lanzz) Date: 2019-11-18 11:14
It looks like choices are broken for nargs='*' even without using default: >>> import argparse >>> parser = argparse.ArgumentParser() >>> parser.add_argument('test', nargs='*', choices=['foo', 'bar', 'baz']) _StoreAction(option_strings=[], dest='test', nargs='*', const=None, default=None, type=None, choices=['foo', 'bar', 'baz'], help=None, metavar=None) >>> parser.parse_args([]) usage: [-h] [{foo,bar,baz} [{foo,bar,baz} ...]] : error: argument test: invalid choice: [] (choose from 'foo', 'bar', 'baz')
msg357064 - (view) Author: Jan Hutař (Jan Hutař) Date: 2019-11-20 11:55
I think there is a same issue with "nargs='+'" - if you are aware of the, please ignore me. $ python3 --version Python 3.7.5 With "choices=...", there seems to be a problem: $ cat bbb.py #!/usr/bin/env python3 import argparse parser = argparse.ArgumentParser() parser.add_argument('choices', nargs='*', default=['a', 'b', 'c'], choices=['a', 'b', 'c']) args = parser.parse_args() print(args) $ ./bbb.py usage: bbb.py [-h] [{a,b,c} [{a,b,c} ...]] bbb.py: error: argument choices: invalid choice: ['a', 'b', 'c'] (choose from 'a', 'b', 'c') $ ./bbb.py a c Namespace(choices=['a', 'c']) but without choices it works: $ cat bbb.py #!/usr/bin/env python3 import argparse parser = argparse.ArgumentParser() parser.add_argument('choices', nargs='*', default=['a', 'b', 'c']) args = parser.parse_args() print(args) $ ./bbb.py Namespace(choices=['a', 'b', 'c']) $ ./bbb.py a c Namespace(choices=['a', 'c']) As I said, if this is a known issue, I'm sorry for the noise.
msg374581 - (view) Author: James Corbett (jameshcorbett) * Date: 2020-07-29 18:22
I would love to get this issue resolved; it seems like everyone agrees that it's a bug. It came up for me recently: https://bugs.python.org/issue41047. Judging from the comments above, the consensus is that the relevant line, `self._check_value(action, value)` should either be replaced with something like `if isinstance(value, collections.abc.Sequence): for v in value: self._check_value(action, v)` or be removed entirely. I think the line should just be removed. I think it's fair to assume that users of `argparse` know what they're doing, so I think they should be allowed to pass default values that conflict with `choices`. Also, removing the line makes the behavior consistent with the optionals, which don't check whether default values are in `choices`. See the below script: ``` import argparse def main(): parser = argparse.ArgumentParser() parser.add_argument("--foo", nargs="+", default=[-1], choices=range(10)) parser.add_argument("--bar", nargs="*", default=-1, choices=range(10)) parser.add_argument("pos", nargs="?", default=-1, choices=range(10)) args = parser.parse_args() print(args) if __name__ == '__main__': main() ``` Which yields: ``` $ python argparse_test.py Namespace(foo=[-1], bar=-1, pos=-1) ```
msg374592 - (view) Author: Raymond Hettinger (rhettinger) * (Python committer) Date: 2020-07-29 23:13
Paul, what is your current thinking on this?
History
Date User Action Args
2022-04-11 14:57:05 admin set github: 53834
2020-12-21 23:36:46 rhettinger set assignee: rhettinger ->
2020-12-01 04:52:56 zumoshi set nosy: + zumoshi
2020-07-29 23:13:20 rhettinger set messages: +
2020-07-29 18:22:56 jameshcorbett set nosy: + jameshcorbettmessages: +
2019-11-20 11:55:54 Jan Hutař set nosy: + Jan Hutařmessages: +
2019-11-18 11:14:30 lanzz set nosy: + lanzzmessages: +
2019-08-30 06:26:19 rhettinger set priority: high -> normalversions: + Python 3.9, - Python 2.7, Python 3.5, Python 3.6
2019-08-30 03:30:47 rhettinger set assignee: bethard -> rhettingernosy: + rhettinger
2019-07-12 13:00:14 João Eiras set nosy: - João Eiras
2019-07-12 12:55:53 João Eiras set nosy: + João Eirasmessages: +
2018-10-16 17:01:43 sebix set nosy: + sebix
2018-04-26 12:05:40 macfreek set nosy: + macfreek
2017-01-20 01:28:04 paul.j3 set messages: +
2017-01-19 23:02:30 paul.j3 set priority: normal -> high
2016-06-12 14:30:54 berker.peksag set nosy: + berker.peksagstage: needs patch -> patch reviewversions: + Python 3.5, Python 3.6, - Python 3.2
2016-06-12 14:30:29 berker.peksag link issue27227 superseder
2015-08-29 10:19:14 deleted250130 set nosy: + deleted250130
2014-05-02 02:04:07 paul.j3 set files: + notes.txt
2014-05-01 04:46:43 paul.j3 set messages: +
2013-07-09 03:07:58 paul.j3 set messages: +
2013-07-03 21:36:35 paul.j3 set files: + issue9625_2.patchmessages: +
2013-06-27 06:33:20 paul.j3 set files: + issue9625_1.patchmessages: +
2013-06-26 20:36:39 paul.j3 set nosy: + paul.j3
2013-06-15 19:02:34 ced set messages: +
2012-11-03 16:00:08 ced set files: + issue9625.patchnosy: + cedmessages: +
2012-07-21 22:19:22 bethard set files: + issue9625.diffkeywords: + patchmessages: +
2012-01-23 22:23:14 thesociable set messages: + title: argparse: Problem with defaults for variable nargs -> argparse: Problem with defaults for variable nargs when using choices
2012-01-23 10:50:51 regis set messages: +
2012-01-22 17:55:46 regis set nosy: + regismessages: +
2010-11-02 22:01:00 eric.araujo set nosy: + eric.araujo
2010-08-21 22:39:06 georg.brandl set assignee: bethard
2010-08-17 19:08:29 eric.smith set nosy: + eric.smith
2010-08-17 13:22:56 bethard set nosy: + bethardstage: needs patchversions: + Python 2.7, Python 3.2, - Python 2.6
2010-08-17 09:19:03 thesociable create