From eccaf69ec3d2b0c63777499e646cd9bba0e07d98 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 24 Apr 2017 10:34:02 +0300 Subject: [PATCH 1/8] bpo-30152: Reduce the number of imports for argparse. --- Lib/argparse.py | 48 +++++++++++++++++++++++-------------- Lib/code.py | 2 +- Lib/collections/__init__.py | 4 ++-- Lib/doctest.py | 2 +- Lib/enum.py | 11 ++++----- Lib/gettext.py | 5 ++-- Lib/http/server.py | 2 +- Lib/locale.py | 6 ++--- Lib/pathlib.py | 2 +- Lib/trace.py | 2 +- Lib/types.py | 7 +++--- Lib/weakref.py | 6 ++--- 12 files changed, 52 insertions(+), 45 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index b69c5adfa072b9..4221695808f7c7 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -84,14 +84,9 @@ import collections as _collections -import copy as _copy import os as _os import re as _re import sys as _sys -import textwrap as _textwrap - -from gettext import gettext as _, ngettext - SUPPRESS = '==SUPPRESS==' @@ -106,6 +101,14 @@ # Utility functions and classes # ============================= +def _(*args): + import gettext + return gettext.gettext(*args) + +def ngettext(*args): + import gettext + return gettext.ngettext(*args) + class _AttributeHolder(object): """Abstract base class that provides __repr__. @@ -137,12 +140,6 @@ def _get_args(self): return [] -def _ensure_value(namespace, name, value): - if getattr(namespace, name, None) is None: - setattr(namespace, name, value) - return getattr(namespace, name) - - # =============== # Formatting Help # =============== @@ -617,12 +614,15 @@ def _iter_indented_subactions(self, action): def _split_lines(self, text, width): text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.wrap(text, width) + import textwrap + return textwrap.wrap(text, width) def _fill_text(self, text, width, indent): text = self._whitespace_matcher.sub(' ', text).strip() - return _textwrap.fill(text, width, initial_indent=indent, - subsequent_indent=indent) + import textwrap + return textwrap.fill(text, width, + initial_indent=indent, + subsequent_indent=indent) def _get_help_string(self, action): return action.help @@ -950,7 +950,12 @@ def __init__(self, metavar=metavar) def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items = getattr(namespace, self.dest, None) + if items is None: + items = [] + else: + import copy + items = copy.copy(items) items.append(values) setattr(namespace, self.dest, items) @@ -976,7 +981,12 @@ def __init__(self, metavar=metavar) def __call__(self, parser, namespace, values, option_string=None): - items = _copy.copy(_ensure_value(namespace, self.dest, [])) + items = getattr(namespace, self.dest, None) + if items is None: + items = [] + else: + import copy + items = copy.copy(items) items.append(self.const) setattr(namespace, self.dest, items) @@ -998,8 +1008,10 @@ def __init__(self, help=help) def __call__(self, parser, namespace, values, option_string=None): - new_count = _ensure_value(namespace, self.dest, 0) + 1 - setattr(namespace, self.dest, new_count) + count = getattr(namespace, self.dest, None) + if count is None: + count = 0 + setattr(namespace, self.dest, count + 1) class _HelpAction(Action): diff --git a/Lib/code.py b/Lib/code.py index 23295f4cf59610..6b12efb3fe4103 100644 --- a/Lib/code.py +++ b/Lib/code.py @@ -7,7 +7,6 @@ import sys import traceback -import argparse from codeop import CommandCompiler, compile_command __all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", @@ -303,6 +302,7 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None): if __name__ == "__main__": + import argparse parser = argparse.ArgumentParser() parser.add_argument('-q', action='store_true', help="don't print version and copyright messages") diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 8408255d27ea6a..8d7c3520b2fee9 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -26,7 +26,6 @@ from operator import itemgetter as _itemgetter, eq as _eq from keyword import iskeyword as _iskeyword import sys as _sys -import heapq as _heapq from _weakref import proxy as _proxy from itertools import repeat as _repeat, chain as _chain, starmap as _starmap from reprlib import recursive_repr as _recursive_repr @@ -551,7 +550,8 @@ def most_common(self, n=None): # Emulate Bag.sortedByCount from Smalltalk if n is None: return sorted(self.items(), key=_itemgetter(1), reverse=True) - return _heapq.nlargest(n, self.items(), key=_itemgetter(1)) + import heapq + return heapq.nlargest(n, self.items(), key=_itemgetter(1)) def elements(self): '''Iterator over elements repeating each as many times as its count. diff --git a/Lib/doctest.py b/Lib/doctest.py index 0b78544d8d0ee0..82b20f4f856108 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -93,7 +93,6 @@ def _test(): ] import __future__ -import argparse import difflib import inspect import linecache @@ -2741,6 +2740,7 @@ def get(self): def _test(): + import argparse parser = argparse.ArgumentParser(description="doctest runner") parser.add_argument('-v', '--verbose', action='store_true', default=False, help='print very verbose output for all tests') diff --git a/Lib/enum.py b/Lib/enum.py index 056400d04c94a1..1146f62d3c3ca8 100644 --- a/Lib/enum.py +++ b/Lib/enum.py @@ -1,7 +1,5 @@ import sys from types import MappingProxyType, DynamicClassAttribute -from functools import reduce -from operator import or_ as _or_ # try _collections first to reduce startup cost try: @@ -744,11 +742,10 @@ def __xor__(self, other): def __invert__(self): members, uncovered = _decompose(self.__class__, self._value_) - inverted_members = [ - m for m in self.__class__ - if m not in members and not m._value_ & self._value_ - ] - inverted = reduce(_or_, inverted_members, self.__class__(0)) + inverted = self.__class__(0) + for m in self.__class__: + if m not in members and not (m._value_ & self._value_): + inverted = inverted | m return self.__class__(inverted) diff --git a/Lib/gettext.py b/Lib/gettext.py index 08d051bf116557..37c4bc97f13090 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -46,11 +46,9 @@ # find this format documented anywhere. -import copy import locale import os import re -import struct import sys from errno import ENOENT @@ -337,7 +335,7 @@ def _get_versions(self, version): def _parse(self, fp): """Override this method to support alternative .mo formats.""" - unpack = struct.unpack + from struct import unpack filename = getattr(fp, 'name', '') # Parse the .mo file header, which consists of 5 little endian 32 # bit words. @@ -528,6 +526,7 @@ def translation(domain, localedir=None, languages=None, # Copy the translation object to allow setting fallbacks and # output charset. All other instance data is shared with the # cached object. + import copy t = copy.copy(t) if codeset: t.set_output_charset(codeset) diff --git a/Lib/http/server.py b/Lib/http/server.py index 429490b73a88b7..cc9a3957f1ef4b 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -87,7 +87,6 @@ "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler", ] -import argparse import copy import datetime import email.utils @@ -1227,6 +1226,7 @@ def test(HandlerClass=BaseHTTPRequestHandler, sys.exit(0) if __name__ == '__main__': + import argparse parser = argparse.ArgumentParser() parser.add_argument('--cgi', action='store_true', help='Run as CGI Server') diff --git a/Lib/locale.py b/Lib/locale.py index 569fe854dbb69c..f1d157d6f9cc57 100644 --- a/Lib/locale.py +++ b/Lib/locale.py @@ -14,10 +14,9 @@ import encodings import encodings.aliases import re -import collections.abc +import _collections_abc from builtins import str as _builtin_str import functools -import warnings # Try importing the _locale module. # @@ -215,7 +214,7 @@ def format_string(f, val, grouping=False, monetary=False): percents = list(_percent_re.finditer(f)) new_f = _percent_re.sub('%s', f) - if isinstance(val, collections.abc.Mapping): + if isinstance(val, _collections_abc.Mapping): new_val = [] for perc in percents: if perc.group()[-1]=='%': @@ -244,6 +243,7 @@ def format_string(f, val, grouping=False, monetary=False): def format(percent, value, grouping=False, monetary=False, *additional): """Deprecated, use format_string instead.""" + import warnings warnings.warn( "This method will be removed in a future version of Python. " "Use 'locale.format_string()' instead.", diff --git a/Lib/pathlib.py b/Lib/pathlib.py index 4368eba8a0efe0..b74023b19f4c27 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -6,7 +6,7 @@ import posixpath import re import sys -from collections.abc import Sequence +from _collections_abc import Sequence from errno import EINVAL, ENOENT, ENOTDIR from operator import attrgetter from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO diff --git a/Lib/trace.py b/Lib/trace.py index ae154615fa3af4..d3962c27ddc998 100755 --- a/Lib/trace.py +++ b/Lib/trace.py @@ -48,7 +48,6 @@ r.write_results(show_missing=True, coverdir="/tmp") """ __all__ = ['Trace', 'CoverageResults'] -import argparse import linecache import os import re @@ -610,6 +609,7 @@ def results(self): def main(): + import argparse parser = argparse.ArgumentParser() parser.add_argument('--version', action='version', version='trace 2.0') diff --git a/Lib/types.py b/Lib/types.py index 1b7859e73a19b9..be006de65dee9f 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -172,9 +172,6 @@ def deleter(self, fdel): return result -import functools as _functools -import collections.abc as _collections_abc - class _GeneratorWrapper: # TODO: Implement this in C. def __init__(self, gen): @@ -247,7 +244,9 @@ def coroutine(func): # return generator-like objects (for instance generators # compiled with Cython). - @_functools.wraps(func) + import functools + import _collections_abc + @functools.wraps(func) def wrapped(*args, **kwargs): coro = func(*args, **kwargs) if (coro.__class__ is CoroutineType or diff --git a/Lib/weakref.py b/Lib/weakref.py index 1802f32a20633b..99de2eab741439 100644 --- a/Lib/weakref.py +++ b/Lib/weakref.py @@ -21,7 +21,7 @@ from _weakrefset import WeakSet, _IterationGuard -import collections.abc # Import after _weakref to avoid circular import. +import _collections_abc # Import after _weakref to avoid circular import. import sys import itertools @@ -87,7 +87,7 @@ def __ne__(self, other): __hash__ = ref.__hash__ -class WeakValueDictionary(collections.abc.MutableMapping): +class WeakValueDictionary(_collections_abc.MutableMapping): """Mapping class that references values weakly. Entries in the dictionary will be discarded when no strong @@ -340,7 +340,7 @@ def __init__(self, ob, callback, key): super().__init__(ob, callback) -class WeakKeyDictionary(collections.abc.MutableMapping): +class WeakKeyDictionary(_collections_abc.MutableMapping): """ Mapping class that references keys weakly. Entries in the dictionary will be discarded when there is no From 79b085f1ed8dcd1f888b685eee44450077e99c42 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 26 Apr 2017 23:48:46 +0300 Subject: [PATCH 2/8] Revert changes in collections. --- Lib/collections/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 8d7c3520b2fee9..8408255d27ea6a 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -26,6 +26,7 @@ from operator import itemgetter as _itemgetter, eq as _eq from keyword import iskeyword as _iskeyword import sys as _sys +import heapq as _heapq from _weakref import proxy as _proxy from itertools import repeat as _repeat, chain as _chain, starmap as _starmap from reprlib import recursive_repr as _recursive_repr @@ -550,8 +551,7 @@ def most_common(self, n=None): # Emulate Bag.sortedByCount from Smalltalk if n is None: return sorted(self.items(), key=_itemgetter(1), reverse=True) - import heapq - return heapq.nlargest(n, self.items(), key=_itemgetter(1)) + return _heapq.nlargest(n, self.items(), key=_itemgetter(1)) def elements(self): '''Iterator over elements repeating each as many times as its count. From 30c87cddc8b54b396460b418594dc9530ffad681 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 26 Apr 2017 23:52:54 +0300 Subject: [PATCH 3/8] Revert movint an argparse import. This will be done in separate PR. --- Lib/code.py | 2 +- Lib/doctest.py | 2 +- Lib/http/server.py | 2 +- Lib/trace.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/code.py b/Lib/code.py index 6b12efb3fe4103..23295f4cf59610 100644 --- a/Lib/code.py +++ b/Lib/code.py @@ -7,6 +7,7 @@ import sys import traceback +import argparse from codeop import CommandCompiler, compile_command __all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", @@ -302,7 +303,6 @@ def interact(banner=None, readfunc=None, local=None, exitmsg=None): if __name__ == "__main__": - import argparse parser = argparse.ArgumentParser() parser.add_argument('-q', action='store_true', help="don't print version and copyright messages") diff --git a/Lib/doctest.py b/Lib/doctest.py index 82b20f4f856108..0b78544d8d0ee0 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -93,6 +93,7 @@ def _test(): ] import __future__ +import argparse import difflib import inspect import linecache @@ -2740,7 +2741,6 @@ def get(self): def _test(): - import argparse parser = argparse.ArgumentParser(description="doctest runner") parser.add_argument('-v', '--verbose', action='store_true', default=False, help='print very verbose output for all tests') diff --git a/Lib/http/server.py b/Lib/http/server.py index cc9a3957f1ef4b..429490b73a88b7 100644 --- a/Lib/http/server.py +++ b/Lib/http/server.py @@ -87,6 +87,7 @@ "SimpleHTTPRequestHandler", "CGIHTTPRequestHandler", ] +import argparse import copy import datetime import email.utils @@ -1226,7 +1227,6 @@ def test(HandlerClass=BaseHTTPRequestHandler, sys.exit(0) if __name__ == '__main__': - import argparse parser = argparse.ArgumentParser() parser.add_argument('--cgi', action='store_true', help='Run as CGI Server') diff --git a/Lib/trace.py b/Lib/trace.py index d3962c27ddc998..ae154615fa3af4 100755 --- a/Lib/trace.py +++ b/Lib/trace.py @@ -48,6 +48,7 @@ r.write_results(show_missing=True, coverdir="/tmp") """ __all__ = ['Trace', 'CoverageResults'] +import argparse import linecache import os import re @@ -609,7 +610,6 @@ def results(self): def main(): - import argparse parser = argparse.ArgumentParser() parser.add_argument('--version', action='version', version='trace 2.0') From 46c77d3a880c150522daec61939dc041276b176c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 28 Apr 2017 08:08:18 +0300 Subject: [PATCH 4/8] Revert moving import gettext. gettext is used in ArgumentParser constructor. --- Lib/argparse.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index 4221695808f7c7..f102dc0f9fb7c9 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -88,6 +88,8 @@ import re as _re import sys as _sys +from gettext import gettext as _, ngettext + SUPPRESS = '==SUPPRESS==' OPTIONAL = '?' @@ -101,14 +103,6 @@ # Utility functions and classes # ============================= -def _(*args): - import gettext - return gettext.gettext(*args) - -def ngettext(*args): - import gettext - return gettext.ngettext(*args) - class _AttributeHolder(object): """Abstract base class that provides __repr__. From d8a622c6cf4d0f52d257052152a3a20485cd1d25 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 28 Apr 2017 08:38:29 +0300 Subject: [PATCH 5/8] Add the comments explaining imports inside functions. --- Lib/argparse.py | 5 +++++ Lib/gettext.py | 4 ++++ Lib/types.py | 1 + 3 files changed, 10 insertions(+) diff --git a/Lib/argparse.py b/Lib/argparse.py index f102dc0f9fb7c9..bf46ae5c84ae99 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -608,6 +608,8 @@ def _iter_indented_subactions(self, action): def _split_lines(self, text, width): text = self._whitespace_matcher.sub(' ', text).strip() + # The textwrap module is used only for formatting help. + # Delay its import for speeding up the common usage of argparse. import textwrap return textwrap.wrap(text, width) @@ -948,6 +950,9 @@ def __call__(self, parser, namespace, values, option_string=None): if items is None: items = [] else: + # The copy module is used only in the 'append' and 'append_const' + # actions. Delay its import for speeding up the case when they + # are not used. import copy items = copy.copy(items) items.append(values) diff --git a/Lib/gettext.py b/Lib/gettext.py index 37c4bc97f13090..a2b3114b4fb36d 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -335,6 +335,8 @@ def _get_versions(self, version): def _parse(self, fp): """Override this method to support alternative .mo formats.""" + # Delay struct import for speeding up gettext import when .mo files + # are not used. from struct import unpack filename = getattr(fp, 'name', '') # Parse the .mo file header, which consists of 5 little endian 32 @@ -526,6 +528,8 @@ def translation(domain, localedir=None, languages=None, # Copy the translation object to allow setting fallbacks and # output charset. All other instance data is shared with the # cached object. + # Delay copy import for speeding up gettext import when .mo files + # are not used. import copy t = copy.copy(t) if codeset: diff --git a/Lib/types.py b/Lib/types.py index be006de65dee9f..3f9484ff2afd38 100644 --- a/Lib/types.py +++ b/Lib/types.py @@ -244,6 +244,7 @@ def coroutine(func): # return generator-like objects (for instance generators # compiled with Cython). + # Delay functools and _collections_abc import for speeding up types import. import functools import _collections_abc @functools.wraps(func) From 578af90a14daa1b6d940500a749941332817835a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 4 May 2017 08:34:36 +0300 Subject: [PATCH 6/8] Import copy only when items is not a list. --- Lib/argparse.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index bf46ae5c84ae99..dfbb2eca4bbbd9 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -134,6 +134,18 @@ def _get_args(self): return [] +def _copy_items(items): + if items is None: + return [] + # The copy module is used only in the 'append' and 'append_const' + # actions, and it is needed only when the default value isn't a list. + # Delay its import for speeding up the common case. + if type(items) is list: + return items[:] + import copy + items = copy.copy(items) + + # =============== # Formatting Help # =============== @@ -947,14 +959,7 @@ def __init__(self, def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) - if items is None: - items = [] - else: - # The copy module is used only in the 'append' and 'append_const' - # actions. Delay its import for speeding up the case when they - # are not used. - import copy - items = copy.copy(items) + items = _copy_items(items) items.append(values) setattr(namespace, self.dest, items) @@ -981,11 +986,7 @@ def __init__(self, def __call__(self, parser, namespace, values, option_string=None): items = getattr(namespace, self.dest, None) - if items is None: - items = [] - else: - import copy - items = copy.copy(items) + items = _copy_items(items) items.append(self.const) setattr(namespace, self.dest, items) From 48a2abf5b37dea3966131cd9a1bab35a7062842a Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Thu, 4 May 2017 10:08:06 +0300 Subject: [PATCH 7/8] Fix a bug in _copy_items(). --- Lib/argparse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/argparse.py b/Lib/argparse.py index dfbb2eca4bbbd9..3fed687721bc33 100644 --- a/Lib/argparse.py +++ b/Lib/argparse.py @@ -143,7 +143,7 @@ def _copy_items(items): if type(items) is list: return items[:] import copy - items = copy.copy(items) + return copy.copy(items) # =============== From 025688db45d05e1b363bdfd9ee0341581c66843b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 24 Sep 2017 10:56:33 +0300 Subject: [PATCH 8/8] Get rid of importing errno. --- Lib/gettext.py | 5 +++-- Lib/os.py | 21 +++++++++------------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Lib/gettext.py b/Lib/gettext.py index 77dd171cad2d49..4c3b80b0239b0a 100644 --- a/Lib/gettext.py +++ b/Lib/gettext.py @@ -50,7 +50,6 @@ import os import re import sys -from errno import ENOENT __all__ = ['NullTranslations', 'GNUTranslations', 'Catalog', @@ -520,7 +519,9 @@ def translation(domain, localedir=None, languages=None, if not mofiles: if fallback: return NullTranslations() - raise OSError(ENOENT, 'No translation file found for domain', domain) + from errno import ENOENT + raise FileNotFoundError(ENOENT, + 'No translation file found for domain', domain) # Avoid opening, reading, and parsing the .mo file after it's been done # once. result = None diff --git a/Lib/os.py b/Lib/os.py index 807ddb56c06558..4f9fdf5b0ead8f 100644 --- a/Lib/os.py +++ b/Lib/os.py @@ -23,7 +23,7 @@ #' import abc -import sys, errno +import sys import stat as st _names = sys.builtin_module_names @@ -590,12 +590,10 @@ def _execvpe(file, args, env=None): argrest = (args,) env = environ - head, tail = path.split(file) - if head: + if path.dirname(file): exec_func(file, *argrest) return - last_exc = saved_exc = None - saved_tb = None + saved_exc = None path_list = get_exec_path(env) if name != 'nt': file = fsencode(file) @@ -604,16 +602,15 @@ def _execvpe(file, args, env=None): fullname = path.join(dir, file) try: exec_func(fullname, *argrest) + except (FileNotFoundError, NotADirectoryError) as e: + last_exc = e except OSError as e: last_exc = e - tb = sys.exc_info()[2] - if (e.errno != errno.ENOENT and e.errno != errno.ENOTDIR - and saved_exc is None): + if saved_exc is None: saved_exc = e - saved_tb = tb - if saved_exc: - raise saved_exc.with_traceback(saved_tb) - raise last_exc.with_traceback(tb) + if saved_exc is not None: + raise saved_exc + raise last_exc def get_exec_path(env=None):