diff --git a/Doc/library/argparse.rst b/Doc/library/argparse.rst index 8ae96311026f33f..0eba8ca932be1fe 100644 --- a/Doc/library/argparse.rst +++ b/Doc/library/argparse.rst @@ -1564,12 +1564,12 @@ it exits and prints the error along with a usage message:: >>> # invalid option >>> parser.parse_args(['--bar']) usage: PROG [-h] [--foo FOO] [bar] - PROG: error: no such option: --bar + PROG: error: unrecognized arguments: --bar >>> # wrong number of arguments >>> parser.parse_args(['spam', 'badger']) usage: PROG [-h] [--foo FOO] [bar] - PROG: error: extra arguments found: badger + PROG: error: unrecognized arguments: badger Arguments containing ``-`` @@ -1606,7 +1606,7 @@ there are no options in the parser that look like negative numbers:: >>> # negative number options present, so -2 is an option >>> parser.parse_args(['-2']) usage: PROG [-h] [-1 ONE] [foo] - PROG: error: no such option: -2 + PROG: error: unrecognized arguments: -2 >>> # negative number options present, so both -1s are options >>> parser.parse_args(['-1', '-1']) diff --git a/Lib/argparse.py b/Lib/argparse.py index fe9fde7f65830fb..38e1c0d0ed78fd6 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -2917,11 +2917,14 @@ def print_help(self, file=None): self._print_message(help_text, file) def _print_message(self, message, file=None): - if message: - file = file or _sys.stderr + if not message: + return + if file is None: + file = _sys.stderr + if file is not None: try: file.write(message) - except (AttributeError, OSError): + except OSError: pass def _get_theme(self, file=None): diff --git a/Lib/test/test_argparse.py b/Lib/test/test_argparse.py index 1193cdca3e4e195..75beb5ede13fefc 100644 --- a/Lib/test/test_argparse.py +++ b/Lib/test/test_argparse.py @@ -80,6 +80,24 @@ def test_skip_invalid_stdout(self): func() self.assertRegex(mocked_stderr.getvalue(), r'usage:') + def test_invalid_file_only(self): + parser = argparse.ArgumentParser() + for func in (parser.print_usage, parser.print_help): + for invalid_f in ("invalid file", "", 0): + with ( + self.subTest(func=func, invalid_f=invalid_f), + self.assertRaises(AttributeError), + ): + func(file=invalid_f) + + def test_exit_when_stderr_oserror(self): + parser = argparse.ArgumentParser() + with (mock.patch('argparse._sys.stderr.write', + side_effect=OSError('not raise this')), + self.assertRaises(SystemExit), + ): + parser.exit(status=0, message='foo') + class TestLazyImports(unittest.TestCase): LAZY_IMPORTS = { diff --git a/Lib/typing.py b/Lib/typing.py index b56ba954ad38be1..65e1d1ea6be5844 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -25,6 +25,7 @@ import collections.abc import copyreg import functools +import keyword import operator import sys import types @@ -998,8 +999,12 @@ def _make_forward_ref(code, *, parent_fwdref=None, **kwargs): if parent_fwdref.__owner__ is not None: kwargs['owner'] = parent_fwdref.__owner__ forward_ref = annotationlib.ForwardRef(code, **kwargs) - # For compatibility, eagerly compile the forwardref's code. - forward_ref.__forward_code__ + # For compatibility, eagerly compile the forwardref's code so that any + # SyntaxError is raised immediately rather than when the forward + # reference is evaluated. Similar to 'ForwardRef.evaluate()', we only compile + # it if necessary: + if not (code.isidentifier() and not keyword.iskeyword(code)): + forward_ref.__forward_code__ return forward_ref diff --git a/Misc/NEWS.d/next/Library/2026-07-18-16-05-38.gh-issue-153967.-OUNXe.rst b/Misc/NEWS.d/next/Library/2026-07-18-16-05-38.gh-issue-153967.-OUNXe.rst new file mode 100644 index 000000000000000..5f520140f43a8ea --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-18-16-05-38.gh-issue-153967.-OUNXe.rst @@ -0,0 +1,3 @@ +:meth:`argparse.ArgumentParser.print_usage` and +:meth:`argparse.ArgumentParser.print_help` won't silently fail when an invalid +file object is specified. Patch by Timothy Poon.