Skip to content

Commit 8d52e97

Browse files
committed
gh-96859: [argparse] Avoid copying lists on every append and extend action
Currently the append, append_const, and extend actions make a copy of the target list every time the action is called. It becomes rather slow with very long option lists. This commit updates parse_known_args() to only copy on the first action call.
1 parent c00964e commit 8d52e97

File tree

1 file changed

+19
-6
lines changed

1 file changed

+19
-6
lines changed

Lib/argparse.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def _get_args(self):
138138
def _copy_items(items):
139139
if items is None:
140140
return []
141-
# The copy module is used only in the 'append' and 'append_const'
141+
# The copy module is used only in the 'append', 'append_const', and 'extend'
142142
# actions, and it is needed only when the default value isn't a list.
143143
# Delay its import for speeding up the common case.
144144
if type(items) is list:
@@ -974,7 +974,13 @@ def __init__(self,
974974
deprecated=deprecated)
975975

976976

977-
class _AppendAction(Action):
977+
class _CloningAction(Action):
978+
979+
def __init__(self, *args, **kwargs):
980+
super().__init__(*args, **kwargs)
981+
982+
983+
class _AppendAction(_CloningAction):
978984

979985
def __init__(self,
980986
option_strings,
@@ -1009,12 +1015,11 @@ def __init__(self,
10091015

10101016
def __call__(self, parser, namespace, values, option_string=None):
10111017
items = getattr(namespace, self.dest, None)
1012-
items = _copy_items(items)
10131018
items.append(values)
10141019
setattr(namespace, self.dest, items)
10151020

10161021

1017-
class _AppendConstAction(Action):
1022+
class _AppendConstAction(_CloningAction):
10181023

10191024
def __init__(self,
10201025
option_strings,
@@ -1038,7 +1043,6 @@ def __init__(self,
10381043

10391044
def __call__(self, parser, namespace, values, option_string=None):
10401045
items = getattr(namespace, self.dest, None)
1041-
items = _copy_items(items)
10421046
items.append(self.const)
10431047
setattr(namespace, self.dest, items)
10441048

@@ -1230,7 +1234,6 @@ def __call__(self, parser, namespace, values, option_string=None):
12301234
class _ExtendAction(_AppendAction):
12311235
def __call__(self, parser, namespace, values, option_string=None):
12321236
items = getattr(namespace, self.dest, None)
1233-
items = _copy_items(items)
12341237
items.extend(values)
12351238
setattr(namespace, self.dest, items)
12361239

@@ -1941,6 +1944,7 @@ def _parse_known_args(self, arg_strings, namespace):
19411944
# converts arg strings to the appropriate and then takes the action
19421945
seen_actions = set()
19431946
seen_non_default_actions = set()
1947+
cloned = set()
19441948
warned = set()
19451949

19461950
def take_action(action, argument_strings, option_string=None):
@@ -1960,6 +1964,15 @@ def take_action(action, argument_strings, option_string=None):
19601964
# take the action if we didn't receive a SUPPRESS value
19611965
# (e.g. from a default)
19621966
if argument_values is not SUPPRESS:
1967+
# copy the destination attribute in case the action modifies it
1968+
# in-place.
1969+
if isinstance(action, _CloningAction) and action not in cloned:
1970+
# only copy on the first action call.
1971+
cloned.add(action)
1972+
1973+
items = getattr(namespace, action.dest, None)
1974+
setattr(namespace, action.dest, _copy_items(items))
1975+
19631976
action(self, namespace, argument_values, option_string)
19641977

19651978
# function to convert arg_strings into an optional action

0 commit comments

Comments
 (0)