diff --git a/Lib/optparse.py b/Lib/optparse.py index bd26909ed14e36..4f52754713c657 100644 --- a/Lib/optparse.py +++ b/Lib/optparse.py @@ -1418,8 +1418,17 @@ def _process_args(self, largs, rargs, values): # value(s) for the last one only) self._process_short_opts(rargs, values) elif self.allow_interspersed_args: + # Move the whole run of positional arguments at once: + # deleting them one at a time from the front is quadratic. largs.append(arg) - del rargs[0] + end = 1 + while end < len(rargs): + arg = rargs[end] + if arg[:1] == "-" and len(arg) > 1: + break + largs.append(arg) + end += 1 + del rargs[:end] else: return # stop now, leave this arg in rargs diff --git a/Lib/test/test_optparse.py b/Lib/test/test_optparse.py index 10ecdd3bccc23f..056c8acb18bdb3 100644 --- a/Lib/test/test_optparse.py +++ b/Lib/test/test_optparse.py @@ -769,6 +769,17 @@ def test_hyphen_becomes_positional_arg(self): {'a': "b", 'boo': None, 'foo': ["bar"]}, ["-"]) + def test_runs_of_positional_args(self): + self.assertParseOK(["x", "y", "-a", "1", "z", "-", "", "w", + "--foo", "2", "v", "-b3", "u", "t"], + {'a': "1", 'boo': 3, 'foo': ["2"]}, + ["x", "y", "z", "-", "", "w", "v", "u", "t"]) + + def test_positional_args_before_separator(self): + self.assertParseOK(["x", "y", "--", "-a", "z"], + {'a': None, 'boo': None, 'foo': None}, + ["x", "y", "-a", "z"]) + def test_no_append_versus_append(self): self.assertParseOK(["-b3", "-b", "5", "--foo=bar", "--foo", "baz"], {'a': None, 'boo': 5, 'foo': ["bar", "baz"]}, @@ -1204,6 +1215,12 @@ def test_callback_meddle_args_separator(self): {'things': [('foo', '--')]}, [2]) + def test_callback_meddle_args_between_positional_args(self): + self.assertParseOK(["a", "b", "-1", "foo", "c", "d", + "-2", "e", "f", "g"], + {'things': [("foo",), ("e", "f")]}, + ["a", "b", 1, "c", "d", 2, "g"]) + class TestCallbackManyArgs(BaseTest): def setUp(self): options = [make_option("-a", "--apple", action="callback", nargs=2, @@ -1283,6 +1300,14 @@ def test_positional_arg_and_variable_args(self): 'c':["foo", "-", "bar"]}, ["hello"]) + def test_positional_args_run_and_variable_args(self): + self.assertParseOK(["hello", "-", "world", "-c", "foo", "bar", + "-b", "x", "y"], + {'a': None, + 'b': True, + 'c': ["foo", "bar"]}, + ["hello", "-", "world", "x", "y"]) + def test_stop_at_option(self): self.assertParseOK(["-c", "foo", "-b"], {'a': None, 'b': True, 'c': ["foo"]}, diff --git a/Misc/NEWS.d/next/Library/2026-11-04-00-40-17.gh-issue-161282.O6vfdQ.rst b/Misc/NEWS.d/next/Library/2026-11-04-00-40-17.gh-issue-161282.O6vfdQ.rst new file mode 100644 index 00000000000000..007683ccf551d2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-04-00-40-17.gh-issue-161282.O6vfdQ.rst @@ -0,0 +1,2 @@ +Fix quadratic time complexity in :meth:`optparse.OptionParser.parse_args` +when parsing many positional arguments.