From 2a6b0f77f74a1f243737f6d4ad7bc91822f6a94f Mon Sep 17 00:00:00 2001 From: Emma Harper Smith Date: Tue, 15 Apr 2025 19:14:33 -0700 Subject: [PATCH 1/7] Introduce `compression` package This commit introduces the `compression` package, specified in PEP 784 to re-export the `lzma`, `bz2`, `gzip`, and `zlib` modules. Introduction of `compression.zstd` will be completed in a future commit once the `_zstd` module is merged. This commit also moves the `_compression` private module to `compression._common.streams`. --- Lib/bz2.py | 8 ++++---- .../_common/streams.py} | 2 +- Lib/compression/bz2/__init__.py | 6 ++++++ Lib/compression/gzip/__init__.py | 6 ++++++ Lib/compression/lzma/__init__.py | 6 ++++++ Lib/compression/zlib/__init__.py | 6 ++++++ Lib/gzip.py | 6 +++--- Lib/lzma.py | 6 +++--- Lib/test/test_bz2.py | 10 +++++----- Lib/test/test_lzma.py | 10 +++++----- 10 files changed, 45 insertions(+), 21 deletions(-) rename Lib/{_compression.py => compression/_common/streams.py} (98%) create mode 100644 Lib/compression/bz2/__init__.py create mode 100644 Lib/compression/gzip/__init__.py create mode 100644 Lib/compression/lzma/__init__.py create mode 100644 Lib/compression/zlib/__init__.py diff --git a/Lib/bz2.py b/Lib/bz2.py index 2420cd019069b4..8d4f365607ad7d 100644 --- a/Lib/bz2.py +++ b/Lib/bz2.py @@ -12,7 +12,7 @@ from builtins import open as _builtin_open import io import os -import _compression +from compression._common import streams from _bz2 import BZ2Compressor, BZ2Decompressor @@ -23,7 +23,7 @@ _MODE_WRITE = 3 -class BZ2File(_compression.BaseStream): +class BZ2File(streams.BaseStream): """A file object providing transparent bzip2 (de)compression. @@ -88,7 +88,7 @@ def __init__(self, filename, mode="r", *, compresslevel=9): raise TypeError("filename must be a str, bytes, file or PathLike object") if self._mode == _MODE_READ: - raw = _compression.DecompressReader(self._fp, + raw = streams.DecompressReader(self._fp, BZ2Decompressor, trailing_error=OSError) self._buffer = io.BufferedReader(raw) else: @@ -248,7 +248,7 @@ def writelines(self, seq): Line separators are not added between the written byte strings. """ - return _compression.BaseStream.writelines(self, seq) + return streams.BaseStream.writelines(self, seq) def seek(self, offset, whence=io.SEEK_SET): """Change the file position. diff --git a/Lib/_compression.py b/Lib/compression/_common/streams.py similarity index 98% rename from Lib/_compression.py rename to Lib/compression/_common/streams.py index e8b70aa0a3e680..9f367d4e30440f 100644 --- a/Lib/_compression.py +++ b/Lib/compression/_common/streams.py @@ -1,4 +1,4 @@ -"""Internal classes used by the gzip, lzma and bz2 modules""" +"""Internal classes used by compression modules""" import io import sys diff --git a/Lib/compression/bz2/__init__.py b/Lib/compression/bz2/__init__.py new file mode 100644 index 00000000000000..76c9a50baa4b17 --- /dev/null +++ b/Lib/compression/bz2/__init__.py @@ -0,0 +1,6 @@ +"""Module re-exporting contents of the bz2 module. + +Please refer to the bz2 documentation for this module's contents. +""" + +from bz2 import * diff --git a/Lib/compression/gzip/__init__.py b/Lib/compression/gzip/__init__.py new file mode 100644 index 00000000000000..7c0f559627b4dc --- /dev/null +++ b/Lib/compression/gzip/__init__.py @@ -0,0 +1,6 @@ +"""Module re-exporting contents of the gzip module. + +Please refer to the gzip documentation for this module's contents. +""" + +from gzip import * diff --git a/Lib/compression/lzma/__init__.py b/Lib/compression/lzma/__init__.py new file mode 100644 index 00000000000000..d0a24163e3fb0d --- /dev/null +++ b/Lib/compression/lzma/__init__.py @@ -0,0 +1,6 @@ +"""Module re-exporting contents of the lzma module. + +Please refer to the lzma documentation for this module's contents. +""" + +from lzma import * diff --git a/Lib/compression/zlib/__init__.py b/Lib/compression/zlib/__init__.py new file mode 100644 index 00000000000000..a4d52f2019d40f --- /dev/null +++ b/Lib/compression/zlib/__init__.py @@ -0,0 +1,6 @@ +"""Module re-exporting contents of the zlib module. + +Please refer to the bz2 documentation for this module's contents. +""" + +from zlib import * diff --git a/Lib/gzip.py b/Lib/gzip.py index 2a6eea1b3939b7..c362f51bd888f9 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -5,7 +5,6 @@ # based on Andrew Kuchling's minigzip.py distributed with the zlib module -import _compression import builtins import io import os @@ -14,6 +13,7 @@ import time import weakref import zlib +from compression._common import streams __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"] @@ -144,7 +144,7 @@ def writable(self): return True -class GzipFile(_compression.BaseStream): +class GzipFile(streams.BaseStream): """The GzipFile class simulates most of the methods of a file object with the exception of the truncate() method. @@ -523,7 +523,7 @@ def _read_gzip_header(fp): return last_mtime -class _GzipReader(_compression.DecompressReader): +class _GzipReader(streams.DecompressReader): def __init__(self, fp): super().__init__(_PaddedFile(fp), zlib._ZlibDecompressor, wbits=-zlib.MAX_WBITS) diff --git a/Lib/lzma.py b/Lib/lzma.py index 946066aa0fba56..7fe93c4d9a06d8 100644 --- a/Lib/lzma.py +++ b/Lib/lzma.py @@ -26,7 +26,7 @@ import os from _lzma import * from _lzma import _encode_filter_properties, _decode_filter_properties # noqa: F401 -import _compression +from compression._common import streams # Value 0 no longer used @@ -35,7 +35,7 @@ _MODE_WRITE = 3 -class LZMAFile(_compression.BaseStream): +class LZMAFile(streams.BaseStream): """A file object providing transparent LZMA (de)compression. @@ -127,7 +127,7 @@ def __init__(self, filename=None, mode="r", *, raise TypeError("filename must be a str, bytes, file or PathLike object") if self._mode == _MODE_READ: - raw = _compression.DecompressReader(self._fp, LZMADecompressor, + raw = streams.DecompressReader(self._fp, LZMADecompressor, trailing_error=LZMAError, format=format, filters=filters) self._buffer = io.BufferedReader(raw) diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py index 7d786be1d25b1c..cdb8a859ae1d10 100644 --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -16,7 +16,7 @@ from test.support import import_helper from test.support import threading_helper from test.support.os_helper import unlink, FakePath -import _compression +from compression._common import streams import sys @@ -126,15 +126,15 @@ def testReadMultiStream(self): def testReadMonkeyMultiStream(self): # Test BZ2File.read() on a multi-stream archive where a stream # boundary coincides with the end of the raw read buffer. - buffer_size = _compression.BUFFER_SIZE - _compression.BUFFER_SIZE = len(self.DATA) + buffer_size = streams.BUFFER_SIZE + streams.BUFFER_SIZE = len(self.DATA) try: self.createTempFile(streams=5) with BZ2File(self.filename) as bz2f: self.assertRaises(TypeError, bz2f.read, float()) self.assertEqual(bz2f.read(), self.TEXT * 5) finally: - _compression.BUFFER_SIZE = buffer_size + streams.BUFFER_SIZE = buffer_size def testReadTrailingJunk(self): self.createTempFile(suffix=self.BAD_DATA) @@ -742,7 +742,7 @@ def testOpenPathLikeFilename(self): def testDecompressLimited(self): """Decompressed data buffering should be limited""" bomb = bz2.compress(b'\0' * int(2e6), compresslevel=9) - self.assertLess(len(bomb), _compression.BUFFER_SIZE) + self.assertLess(len(bomb), streams.BUFFER_SIZE) decomp = BZ2File(BytesIO(bomb)) self.assertEqual(decomp.read(1), b'\0') diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index 4dd10faf71360a..267c06cfbb8e78 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -1,4 +1,3 @@ -import _compression import array from io import BytesIO, UnsupportedOperation, DEFAULT_BUFFER_SIZE import os @@ -7,6 +6,7 @@ import sys from test import support import unittest +from compression._common import streams from test.support import _4G, bigmemtest from test.support.import_helper import import_module @@ -861,13 +861,13 @@ def test_read_multistream(self): def test_read_multistream_buffer_size_aligned(self): # Test the case where a stream boundary coincides with the end # of the raw read buffer. - saved_buffer_size = _compression.BUFFER_SIZE - _compression.BUFFER_SIZE = len(COMPRESSED_XZ) + saved_buffer_size = streams.BUFFER_SIZE + streams.BUFFER_SIZE = len(COMPRESSED_XZ) try: with LZMAFile(BytesIO(COMPRESSED_XZ * 5)) as f: self.assertEqual(f.read(), INPUT * 5) finally: - _compression.BUFFER_SIZE = saved_buffer_size + streams.BUFFER_SIZE = saved_buffer_size def test_read_trailing_junk(self): with LZMAFile(BytesIO(COMPRESSED_XZ + COMPRESSED_BOGUS)) as f: @@ -1066,7 +1066,7 @@ def test_readlines(self): def test_decompress_limited(self): """Decompressed data buffering should be limited""" bomb = lzma.compress(b'\0' * int(2e6), preset=6) - self.assertLess(len(bomb), _compression.BUFFER_SIZE) + self.assertLess(len(bomb), streams.BUFFER_SIZE) decomp = LZMAFile(BytesIO(bomb)) self.assertEqual(decomp.read(1), b'\0') From 0d7e1db155f3ce57069c41c71143ab748a50438c Mon Sep 17 00:00:00 2001 From: Emma Harper Smith Date: Sat, 26 Apr 2025 11:27:30 -0700 Subject: [PATCH 2/7] Update stdlib modules list Also update `generate_stdlib_module_names.py` to collect `compression`, a namespace package. --- Python/stdlib_module_names.h | 2 +- Tools/build/generate_stdlib_module_names.py | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Python/stdlib_module_names.h b/Python/stdlib_module_names.h index 26f6272ae9cfbc..fcef7419bd397b 100644 --- a/Python/stdlib_module_names.h +++ b/Python/stdlib_module_names.h @@ -24,7 +24,6 @@ static const char* _Py_stdlib_module_names[] = { "_collections_abc", "_colorize", "_compat_pickle", -"_compression", "_contextvars", "_csv", "_ctypes", @@ -128,6 +127,7 @@ static const char* _Py_stdlib_module_names[] = { "collections", "colorsys", "compileall", +"compression", "concurrent", "configparser", "contextlib", diff --git a/Tools/build/generate_stdlib_module_names.py b/Tools/build/generate_stdlib_module_names.py index 9873890837fa8e..0e3ba35d867e2c 100644 --- a/Tools/build/generate_stdlib_module_names.py +++ b/Tools/build/generate_stdlib_module_names.py @@ -70,8 +70,16 @@ def list_packages(names): package_path = os.path.join(STDLIB_PATH, name) if not os.path.isdir(package_path): continue - if any(package_file.endswith(".py") - for package_file in os.listdir(package_path)): + # Walk the package directory to check if the package is a namespace + has_py_files = False + for root, _dirs, files in os.walk(package_path): + for file in files: + if file.endswith(".py"): + has_py_files = True + break + if has_py_files: + break + if has_py_files: names.add(name) From 095abfee34df9d3176030d8456fdefa46cbe2f97 Mon Sep 17 00:00:00 2001 From: Emma Harper Smith Date: Sat, 26 Apr 2025 11:31:33 -0700 Subject: [PATCH 3/7] Sort lzma import in alphabetical order --- Lib/lzma.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/lzma.py b/Lib/lzma.py index 7fe93c4d9a06d8..94e83538e4035e 100644 --- a/Lib/lzma.py +++ b/Lib/lzma.py @@ -24,9 +24,9 @@ import builtins import io import os +from compression._common import streams from _lzma import * from _lzma import _encode_filter_properties, _decode_filter_properties # noqa: F401 -from compression._common import streams # Value 0 no longer used From 073113033a9d3842416b7d58e8dc57a64ede3c57 Mon Sep 17 00:00:00 2001 From: Emma Harper Smith Date: Sat, 26 Apr 2025 12:07:51 -0700 Subject: [PATCH 4/7] More import sorting --- Lib/bz2.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/bz2.py b/Lib/bz2.py index 8d4f365607ad7d..ddd9f9645975f3 100644 --- a/Lib/bz2.py +++ b/Lib/bz2.py @@ -10,9 +10,9 @@ __author__ = "Nadeem Vawda " from builtins import open as _builtin_open +from compression._common import streams import io import os -from compression._common import streams from _bz2 import BZ2Compressor, BZ2Decompressor From c8201ba9e058e3fad060fe13e91800a5017f037c Mon Sep 17 00:00:00 2001 From: Emma Harper Smith Date: Sat, 26 Apr 2025 14:44:56 -0700 Subject: [PATCH 5/7] Rename compression._common.streams to compression._common._streams --- Lib/bz2.py | 8 ++++---- Lib/compression/_common/{streams.py => _streams.py} | 0 Lib/gzip.py | 6 +++--- Lib/lzma.py | 6 +++--- Lib/test/test_bz2.py | 10 +++++----- Lib/test/test_lzma.py | 10 +++++----- 6 files changed, 20 insertions(+), 20 deletions(-) rename Lib/compression/_common/{streams.py => _streams.py} (100%) diff --git a/Lib/bz2.py b/Lib/bz2.py index ddd9f9645975f3..eb58f4da596ea1 100644 --- a/Lib/bz2.py +++ b/Lib/bz2.py @@ -10,7 +10,7 @@ __author__ = "Nadeem Vawda " from builtins import open as _builtin_open -from compression._common import streams +from compression._common import _streams import io import os @@ -23,7 +23,7 @@ _MODE_WRITE = 3 -class BZ2File(streams.BaseStream): +class BZ2File(_streams.BaseStream): """A file object providing transparent bzip2 (de)compression. @@ -88,7 +88,7 @@ def __init__(self, filename, mode="r", *, compresslevel=9): raise TypeError("filename must be a str, bytes, file or PathLike object") if self._mode == _MODE_READ: - raw = streams.DecompressReader(self._fp, + raw = _streams.DecompressReader(self._fp, BZ2Decompressor, trailing_error=OSError) self._buffer = io.BufferedReader(raw) else: @@ -248,7 +248,7 @@ def writelines(self, seq): Line separators are not added between the written byte strings. """ - return streams.BaseStream.writelines(self, seq) + return _streams.BaseStream.writelines(self, seq) def seek(self, offset, whence=io.SEEK_SET): """Change the file position. diff --git a/Lib/compression/_common/streams.py b/Lib/compression/_common/_streams.py similarity index 100% rename from Lib/compression/_common/streams.py rename to Lib/compression/_common/_streams.py diff --git a/Lib/gzip.py b/Lib/gzip.py index c362f51bd888f9..b7375b2547314f 100644 --- a/Lib/gzip.py +++ b/Lib/gzip.py @@ -13,7 +13,7 @@ import time import weakref import zlib -from compression._common import streams +from compression._common import _streams __all__ = ["BadGzipFile", "GzipFile", "open", "compress", "decompress"] @@ -144,7 +144,7 @@ def writable(self): return True -class GzipFile(streams.BaseStream): +class GzipFile(_streams.BaseStream): """The GzipFile class simulates most of the methods of a file object with the exception of the truncate() method. @@ -523,7 +523,7 @@ def _read_gzip_header(fp): return last_mtime -class _GzipReader(streams.DecompressReader): +class _GzipReader(_streams.DecompressReader): def __init__(self, fp): super().__init__(_PaddedFile(fp), zlib._ZlibDecompressor, wbits=-zlib.MAX_WBITS) diff --git a/Lib/lzma.py b/Lib/lzma.py index 94e83538e4035e..316066d024ea02 100644 --- a/Lib/lzma.py +++ b/Lib/lzma.py @@ -24,7 +24,7 @@ import builtins import io import os -from compression._common import streams +from compression._common import _streams from _lzma import * from _lzma import _encode_filter_properties, _decode_filter_properties # noqa: F401 @@ -35,7 +35,7 @@ _MODE_WRITE = 3 -class LZMAFile(streams.BaseStream): +class LZMAFile(_streams.BaseStream): """A file object providing transparent LZMA (de)compression. @@ -127,7 +127,7 @@ def __init__(self, filename=None, mode="r", *, raise TypeError("filename must be a str, bytes, file or PathLike object") if self._mode == _MODE_READ: - raw = streams.DecompressReader(self._fp, LZMADecompressor, + raw = _streams.DecompressReader(self._fp, LZMADecompressor, trailing_error=LZMAError, format=format, filters=filters) self._buffer = io.BufferedReader(raw) diff --git a/Lib/test/test_bz2.py b/Lib/test/test_bz2.py index cdb8a859ae1d10..f32b24b39bad00 100644 --- a/Lib/test/test_bz2.py +++ b/Lib/test/test_bz2.py @@ -16,7 +16,7 @@ from test.support import import_helper from test.support import threading_helper from test.support.os_helper import unlink, FakePath -from compression._common import streams +from compression._common import _streams import sys @@ -126,15 +126,15 @@ def testReadMultiStream(self): def testReadMonkeyMultiStream(self): # Test BZ2File.read() on a multi-stream archive where a stream # boundary coincides with the end of the raw read buffer. - buffer_size = streams.BUFFER_SIZE - streams.BUFFER_SIZE = len(self.DATA) + buffer_size = _streams.BUFFER_SIZE + _streams.BUFFER_SIZE = len(self.DATA) try: self.createTempFile(streams=5) with BZ2File(self.filename) as bz2f: self.assertRaises(TypeError, bz2f.read, float()) self.assertEqual(bz2f.read(), self.TEXT * 5) finally: - streams.BUFFER_SIZE = buffer_size + _streams.BUFFER_SIZE = buffer_size def testReadTrailingJunk(self): self.createTempFile(suffix=self.BAD_DATA) @@ -742,7 +742,7 @@ def testOpenPathLikeFilename(self): def testDecompressLimited(self): """Decompressed data buffering should be limited""" bomb = bz2.compress(b'\0' * int(2e6), compresslevel=9) - self.assertLess(len(bomb), streams.BUFFER_SIZE) + self.assertLess(len(bomb), _streams.BUFFER_SIZE) decomp = BZ2File(BytesIO(bomb)) self.assertEqual(decomp.read(1), b'\0') diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index 267c06cfbb8e78..d7e8327cfee18a 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -6,7 +6,7 @@ import sys from test import support import unittest -from compression._common import streams +from compression._common import _streams from test.support import _4G, bigmemtest from test.support.import_helper import import_module @@ -861,13 +861,13 @@ def test_read_multistream(self): def test_read_multistream_buffer_size_aligned(self): # Test the case where a stream boundary coincides with the end # of the raw read buffer. - saved_buffer_size = streams.BUFFER_SIZE - streams.BUFFER_SIZE = len(COMPRESSED_XZ) + saved_buffer_size = _streams.BUFFER_SIZE + _streams.BUFFER_SIZE = len(COMPRESSED_XZ) try: with LZMAFile(BytesIO(COMPRESSED_XZ * 5)) as f: self.assertEqual(f.read(), INPUT * 5) finally: - streams.BUFFER_SIZE = saved_buffer_size + _streams.BUFFER_SIZE = saved_buffer_size def test_read_trailing_junk(self): with LZMAFile(BytesIO(COMPRESSED_XZ + COMPRESSED_BOGUS)) as f: @@ -1066,7 +1066,7 @@ def test_readlines(self): def test_decompress_limited(self): """Decompressed data buffering should be limited""" bomb = lzma.compress(b'\0' * int(2e6), preset=6) - self.assertLess(len(bomb), streams.BUFFER_SIZE) + self.assertLess(len(bomb), _streams.BUFFER_SIZE) decomp = LZMAFile(BytesIO(bomb)) self.assertEqual(decomp.read(1), b'\0') From e7fed14b44ab4e3ad77379af6bbf90b41d2348f4 Mon Sep 17 00:00:00 2001 From: Emma Harper Smith Date: Sat, 26 Apr 2025 14:45:47 -0700 Subject: [PATCH 6/7] Re-export existing module docstrings --- Lib/compression/bz2/__init__.py | 7 +++---- Lib/compression/gzip/__init__.py | 7 +++---- Lib/compression/lzma/__init__.py | 7 +++---- Lib/compression/zlib/__init__.py | 7 +++---- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/Lib/compression/bz2/__init__.py b/Lib/compression/bz2/__init__.py index 76c9a50baa4b17..16815d6cd20e5b 100644 --- a/Lib/compression/bz2/__init__.py +++ b/Lib/compression/bz2/__init__.py @@ -1,6 +1,5 @@ -"""Module re-exporting contents of the bz2 module. - -Please refer to the bz2 documentation for this module's contents. -""" +import bz2 +__doc__ = bz2.__doc__ +del bz2 from bz2 import * diff --git a/Lib/compression/gzip/__init__.py b/Lib/compression/gzip/__init__.py index 7c0f559627b4dc..552f48f948ae47 100644 --- a/Lib/compression/gzip/__init__.py +++ b/Lib/compression/gzip/__init__.py @@ -1,6 +1,5 @@ -"""Module re-exporting contents of the gzip module. - -Please refer to the gzip documentation for this module's contents. -""" +import gzip +__doc__ = gzip.__doc__ +del gzip from gzip import * diff --git a/Lib/compression/lzma/__init__.py b/Lib/compression/lzma/__init__.py index d0a24163e3fb0d..b4bc7ccb1db3cf 100644 --- a/Lib/compression/lzma/__init__.py +++ b/Lib/compression/lzma/__init__.py @@ -1,6 +1,5 @@ -"""Module re-exporting contents of the lzma module. - -Please refer to the lzma documentation for this module's contents. -""" +import lzma +__doc__ = lzma.__doc__ +del lzma from lzma import * diff --git a/Lib/compression/zlib/__init__.py b/Lib/compression/zlib/__init__.py index a4d52f2019d40f..3aa7e2db90e862 100644 --- a/Lib/compression/zlib/__init__.py +++ b/Lib/compression/zlib/__init__.py @@ -1,6 +1,5 @@ -"""Module re-exporting contents of the zlib module. - -Please refer to the bz2 documentation for this module's contents. -""" +import zlib +__doc__ = zlib.__doc__ +del zlib from zlib import * From 2e1b2a8ddc718c603aef13969c6bbfa2bdef0f8f Mon Sep 17 00:00:00 2001 From: Emma Harper Smith Date: Sat, 26 Apr 2025 16:34:49 -0700 Subject: [PATCH 7/7] Add compression/__init__.py and revert tooling change --- Lib/compression/__init__.py | 0 Tools/build/generate_stdlib_module_names.py | 12 ++---------- 2 files changed, 2 insertions(+), 10 deletions(-) create mode 100644 Lib/compression/__init__.py diff --git a/Lib/compression/__init__.py b/Lib/compression/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/Tools/build/generate_stdlib_module_names.py b/Tools/build/generate_stdlib_module_names.py index 0e3ba35d867e2c..9873890837fa8e 100644 --- a/Tools/build/generate_stdlib_module_names.py +++ b/Tools/build/generate_stdlib_module_names.py @@ -70,16 +70,8 @@ def list_packages(names): package_path = os.path.join(STDLIB_PATH, name) if not os.path.isdir(package_path): continue - # Walk the package directory to check if the package is a namespace - has_py_files = False - for root, _dirs, files in os.walk(package_path): - for file in files: - if file.endswith(".py"): - has_py_files = True - break - if has_py_files: - break - if has_py_files: + if any(package_file.endswith(".py") + for package_file in os.listdir(package_path)): names.add(name)