gh-154904: Speed up import of shutil by probing compression extensions directly (#154908)

emmatyping <[email protected]> Tue, 11 Aug 2026 00:16:18 -0400 (EDT)
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/b98e1a5baa8a721705c27a481e6ecc30babb02e7
commit: b98e1a5baa8a721705c27a481e6ecc30babb02e7
branch: main
author: Maxime David <[email protected]>
committer: emmatyping <[email protected]>
date: 2026-08-10T21:16:03-07:00
summary:

gh-154904: Speed up import of shutil by probing compression extensions directly (#154908)

files:
A Misc/NEWS.d/next/Library/2026-07-30-02-15-00.gh-issue-154904.Kx9mAq.rst
M Lib/shutil.py
M Lib/test/test_shutil.py

diff --git a/Lib/shutil.py b/Lib/shutil.py
index ce6969d6a4bf5a..ab75ba9da8894b 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -18,23 +18,28 @@
 except ImportError:
     _ZLIB_SUPPORTED = False
 
+# bz2, lzma and compression.zstd are pure Python wrappers whose only
+# importable dependency that may be missing is the extension module they
+# wrap.  Probe those extensions directly instead: it gives the same answer
+# without executing the wrappers, which shutil only needs when an archive
+# is actually created or extracted.
 try:
-    import bz2
-    del bz2
+    import _bz2
+    del _bz2
     _BZ2_SUPPORTED = True
 except ImportError:
     _BZ2_SUPPORTED = False
 
 try:
-    import lzma
-    del lzma
+    import _lzma
+    del _lzma
     _LZMA_SUPPORTED = True
 except ImportError:
     _LZMA_SUPPORTED = False
 
 try:
-    from compression import zstd
-    del zstd
+    import _zstd
+    del _zstd
     _ZSTD_SUPPORTED = True
 except ImportError:
     _ZSTD_SUPPORTED = False
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index d6b3b6a642bee1..06ebdf9b68f20f 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -32,6 +32,7 @@
 from test import support
 from test.support import os_helper, socket_helper
 from test.support.os_helper import TESTFN, FakePath
+from test.support.import_helper import ensure_lazy_imports
 
 TESTFN2 = TESTFN + "2"
 TESTFN_SRC = TESTFN + "_SRC"
@@ -2362,6 +2363,14 @@ def _boo(filename, extract_dir, extra):
         unregister_unpack_format('Boo2')
         self.assertEqual(get_unpack_formats(), formats)
 
+    def test_compression_wrappers_not_imported_by_shutil(self):
+        # gh-154904: Importing shutil must not pull in the compression
+        # wrappers: they are only needed once an archive is actually created
+        # or extracted, and importing them measurably slows down every
+        # process that uses shutil.
+        ensure_lazy_imports("shutil",
+                            {"bz2", "lzma", "compression", "compression.zstd"})
+
 
 class TestMisc(BaseTest, unittest.TestCase):
 
diff --git a/Misc/NEWS.d/next/Library/2026-07-30-02-15-00.gh-issue-154904.Kx9mAq.rst b/Misc/NEWS.d/next/Library/2026-07-30-02-15-00.gh-issue-154904.Kx9mAq.rst
new file mode 100644
index 00000000000000..fa3f930dc21b86
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-30-02-15-00.gh-issue-154904.Kx9mAq.rst
@@ -0,0 +1,4 @@
+Speed up :mod:`shutil` import by probing the ``_bz2``, ``_lzma`` and
+``_zstd`` extension modules instead of importing the :mod:`bz2`,
+:mod:`lzma` and :mod:`compression.zstd` wrappers, which are now only
+imported when an archive is actually created or extracted.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]