This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: argparse crashes in gettext when processing missing arguments
Type: crash Stage: resolved
Components: Library (Lib) Versions: Python 3.7
process
Status: closed Resolution: fixed
Dependencies: Superseder:
Assigned To: Nosy List: Anthony Sottile, bethard, corona10, eric.fahlgren
Priority: normal Keywords:

Created on 2019-01-19 17:38 by eric.fahlgren, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
foo.mo eric.fahlgren, 2019-01-19 22:44 Binary message translation file, put in 'en_US/LC_MESSAGES/'
Messages (7)
msg334065 - (view) Author: Eric Fahlgren (eric.fahlgren) * Date: 2019-01-19 17:38
When argparse is configured with an option that takes arguments, then the script is invoked with the switch but no arguments, a nonsensical exception is raised during gettext processing.

In the 3.7.1 source, the error is at line 2077 of argparse.py, where 'action.nargs' is not an integer as expected by 'ngettext', but one of None, '*' or '?':

            default = ngettext('expected %s argument',
                               'expected %s arguments',
                               action.nargs) % action.nargs
            msg = nargs_errors.get(action.nargs, default)

Fix should be pretty trivial, swap the two lines and if 'get' produces None, only then compute the default.

  File "C:\Program Files\Python37\lib\argparse.py", line 1749, in parse_args
    args, argv = self.parse_known_args(args, namespace)
  File "C:\Program Files\Python37\lib\argparse.py", line 1781, in parse_known_args
    namespace, args = self._parse_known_args(args, namespace)
  File "C:\Program Files\Python37\lib\argparse.py", line 1987, in _parse_known_args
    start_index = consume_optional(start_index)
  File "C:\Program Files\Python37\lib\argparse.py", line 1917, in consume_optional
    arg_count = match_argument(action, selected_patterns)
  File "C:\Program Files\Python37\lib\argparse.py", line 2079, in _match_argument
    action.nargs) % action.nargs
  File "C:\Program Files\Python37\lib\gettext.py", line 631, in ngettext
    return dngettext(_current_domain, msgid1, msgid2, n)
  File "C:\Program Files\Python37\lib\gettext.py", line 610, in dngettext
    return t.ngettext(msgid1, msgid2, n)
  File "C:\Program Files\Python37\lib\gettext.py", line 462, in ngettext
    tmsg = self._catalog[(msgid1, self.plural(n))]
  File "<string>", line 4, in func
  File "C:\Program Files\Python37\lib\gettext.py", line 168, in _as_int
    (n.__class__.__name__,)) from None
TypeError: Plural value must be an integer, got NoneType
msg334069 - (view) Author: Anthony Sottile (Anthony Sottile) * Date: 2019-01-19 21:19
Can you provide a reproducer? I'm having difficulty reproducing with this script:

import argparse

p = argparse.ArgumentParser()
p.add_argument('--foo', nargs=None)
args = p.parse_args()


$ python3.7 --version
Python 3.7.2
$ python3.7 t.py --foo
usage: t.py [-h] [--foo FOO]
t.py: error: argument --foo: expected one argument
msg334074 - (view) Author: Eric Fahlgren (eric.fahlgren) * Date: 2019-01-19 22:44
After a bit more digging, it's a side effect of having the locale set with 'Plural-Forms'.  I've attached the resulting .mo file, but since it's a binary, I'm not sure it will work cross-platform, so here's how to recreate it.

> cat en_US/LC_MESSAGES/foo.po
msgid ""
msgstr "Plural-Forms: nplurals=2; plural=(n != 1);\n"

> python /Python37/Tools/i18n/msgfmt.py en_US/LC_MESSAGES/foo.po
> ll en_US/LC_MESSAGES/
-rwx------+ 1 efahlgren Domain Users 89 2019-01-19 14:36 foo.mo*
-rw-r--r--+ 1 efahlgren Domain Users 69 2019-01-19 14:34 foo.po

Then you can reproduce with some setup in your script:

import os
import gettext
import argparse

os.putenv('LANG', 'en_US')  # Just to make sure.
gettext.bindtextdomain('foo', '.')
gettext.textdomain('foo')

p = argparse.ArgumentParser()
p.add_argument('--foo', nargs=None)
p.parse_args()
msg334076 - (view) Author: Dong-hee Na (corona10) * (Python committer) Date: 2019-01-20 03:25
No crash at Python 3.7.2+ (heads/3.7:47290e7642, Jan 20 2019, 12:22:44)
and master branch
msg334077 - (view) Author: Dong-hee Na (corona10) * (Python committer) Date: 2019-01-20 03:46
Umm looks like I should pass an argument on cmd to reproduce it. I will try to reproduce it later
msg334080 - (view) Author: Dong-hee Na (corona10) * (Python committer) Date: 2019-01-20 09:10
➜  cpython.git git:(3.7) ✗ ./python.exe bpo-35785.py --foo test
➜  cpython.git git:(3.7) ✗ ./python.exe bpo-35785.py --foo 1
➜  cpython.git git:(3.7) ✗ ./python.exe bpo-35785.py --foo
usage: bpo-35785.py [-h] [--foo FOO]
bpo-35785.py: error: argument --foo: expected one argument
msg334093 - (view) Author: Eric Fahlgren (eric.fahlgren) * Date: 2019-01-20 17:24
Thanks, I installed 3.7.2 on one of our non-production machines and it appears that gettext has been fixed, so I'm closing this.

> python -V
Python 3.7.2
> python bpo35785.py --foo
usage: bpo35785.py [-h] [--foo FOO]
bpo35785.py: error: argument --foo: expected one argument
History
Date User Action Args
2022-04-11 14:59:10adminsetgithub: 79966
2019-01-20 17:24:13eric.fahlgrensetstatus: open -> closed
resolution: fixed
messages: + msg334093

stage: resolved
2019-01-20 09:10:37corona10setmessages: + msg334080
2019-01-20 03:46:48corona10setmessages: + msg334077
2019-01-20 03:25:17corona10setnosy: + corona10
messages: + msg334076
2019-01-19 22:44:45eric.fahlgrensetfiles: + foo.mo

messages: + msg334074
2019-01-19 21:19:48Anthony Sottilesetnosy: + Anthony Sottile
messages: + msg334069
2019-01-19 18:08:06xtreaksetnosy: + bethard
2019-01-19 17:38:37eric.fahlgrensettype: crash
2019-01-19 17:38:06eric.fahlgrencreate