From 6ed477458544f3945f1cf574f591ad7561bd5c5d Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 7 Jul 2025 22:13:40 +0900 Subject: [PATCH 01/13] Fix import behavior for concurrent.futures.InterpreterPoolExecutor --- Lib/concurrent/futures/__init__.py | 10 +++++++--- .../2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py index 7ada7431c1ab8c..fa3ef0c2a206a5 100644 --- a/Lib/concurrent/futures/__init__.py +++ b/Lib/concurrent/futures/__init__.py @@ -39,8 +39,12 @@ def __dir__(): return __all__ + ('__author__', '__doc__') +_no_interpreter_pool_executor = False + + def __getattr__(name): global ProcessPoolExecutor, ThreadPoolExecutor, InterpreterPoolExecutor + global _no_interpreter_pool_executor if name == 'ProcessPoolExecutor': from .process import ProcessPoolExecutor as pe @@ -52,13 +56,13 @@ def __getattr__(name): ThreadPoolExecutor = te return te - if name == 'InterpreterPoolExecutor': + if name == 'InterpreterPoolExecutor' and not _no_interpreter_pool_executor: try: from .interpreter import InterpreterPoolExecutor as ie except ModuleNotFoundError: - ie = InterpreterPoolExecutor = None + _no_interpreter_pool_executor = True else: InterpreterPoolExecutor = ie - return ie + return ie raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst b/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst new file mode 100644 index 00000000000000..ec2b8a74e58285 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst @@ -0,0 +1,3 @@ +Raises :exc:`AttributeError` when accessing +``concurrent.futures.InterpreterPoolExecutor`` and :mod:`_interpreter` is +not available. From cc1a90a40b8a38cd1a3d506e5287257d6f4a5f85 Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 7 Jul 2025 22:26:33 +0900 Subject: [PATCH 02/13] Fix rst marker --- .../next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst b/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst index ec2b8a74e58285..13dd90e3de6443 100644 --- a/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst +++ b/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst @@ -1,3 +1,3 @@ Raises :exc:`AttributeError` when accessing -``concurrent.futures.InterpreterPoolExecutor`` and :mod:`_interpreter` is +``concurrent.futures.InterpreterPoolExecutor`` and ``_interpreter`` is not available. From 7508c7376e71f71fc2523d1f9afd7cd6a6f99417 Mon Sep 17 00:00:00 2001 From: AN Long Date: Mon, 7 Jul 2025 22:57:13 +0900 Subject: [PATCH 03/13] Fix import start --- Lib/concurrent/futures/__init__.py | 33 ++++++++++++++++-------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py index fa3ef0c2a206a5..c7d8f83c1af1e2 100644 --- a/Lib/concurrent/futures/__init__.py +++ b/Lib/concurrent/futures/__init__.py @@ -17,7 +17,7 @@ wait, as_completed) -__all__ = ( +__all__ = [ 'FIRST_COMPLETED', 'FIRST_EXCEPTION', 'ALL_COMPLETED', @@ -29,22 +29,29 @@ 'Executor', 'wait', 'as_completed', - 'InterpreterPoolExecutor', 'ProcessPoolExecutor', 'ThreadPoolExecutor', -) +] -def __dir__(): - return __all__ + ('__author__', '__doc__') +_have__interpreters = False + +try: + import _interpreters + _have__interpreters = True +except ModuleNotFoundError: + pass +if _have__interpreters: + __all__.append('InterpreterPoolExecutor') -_no_interpreter_pool_executor = False + +def __dir__(): + return __all__ + ('__author__', '__doc__') def __getattr__(name): global ProcessPoolExecutor, ThreadPoolExecutor, InterpreterPoolExecutor - global _no_interpreter_pool_executor if name == 'ProcessPoolExecutor': from .process import ProcessPoolExecutor as pe @@ -56,13 +63,9 @@ def __getattr__(name): ThreadPoolExecutor = te return te - if name == 'InterpreterPoolExecutor' and not _no_interpreter_pool_executor: - try: - from .interpreter import InterpreterPoolExecutor as ie - except ModuleNotFoundError: - _no_interpreter_pool_executor = True - else: - InterpreterPoolExecutor = ie - return ie + if _have__interpreters and name == 'InterpreterPoolExecutor': + from .interpreter import InterpreterPoolExecutor as ie + InterpreterPoolExecutor = ie + return ie raise AttributeError(f"module {__name__!r} has no attribute {name!r}") From fe2c921a3a9ce810b1bec2146a40790dd852d9ac Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 01:20:06 +0900 Subject: [PATCH 04/13] Update Lib/concurrent/futures/__init__.py Co-authored-by: Serhiy Storchaka --- Lib/concurrent/futures/__init__.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py index c7d8f83c1af1e2..5cf092485d3793 100644 --- a/Lib/concurrent/futures/__init__.py +++ b/Lib/concurrent/futures/__init__.py @@ -64,8 +64,7 @@ def __getattr__(name): return te if _have__interpreters and name == 'InterpreterPoolExecutor': - from .interpreter import InterpreterPoolExecutor as ie - InterpreterPoolExecutor = ie - return ie + from .interpreter import InterpreterPoolExecutor + return InterpreterPoolExecutor raise AttributeError(f"module {__name__!r} has no attribute {name!r}") From 0f1c7b2a6d63e6d7eac8439579a0294b55e686bb Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 01:21:01 +0900 Subject: [PATCH 05/13] Update Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst Co-authored-by: Peter Bierma --- .../next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst b/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst index 13dd90e3de6443..4ac04b9c0a6c5c 100644 --- a/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst +++ b/Misc/NEWS.d/next/Library/2025-07-07-22-12-32.gh-issue-136380.1b_nXl.rst @@ -1,3 +1,3 @@ Raises :exc:`AttributeError` when accessing -``concurrent.futures.InterpreterPoolExecutor`` and ``_interpreter`` is +:class:`concurrent.futures.InterpreterPoolExecutor` and subinterpreters are not available. From 30a34ddd8803bb9257bd046a8cad31406ea13597 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 01:22:10 +0900 Subject: [PATCH 06/13] Simplify the try import codes --- Lib/concurrent/futures/__init__.py | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py index 5cf092485d3793..b6f0985058ea75 100644 --- a/Lib/concurrent/futures/__init__.py +++ b/Lib/concurrent/futures/__init__.py @@ -34,15 +34,14 @@ ] -_have__interpreters = False +_interpreters = None try: import _interpreters - _have__interpreters = True except ModuleNotFoundError: pass -if _have__interpreters: +if _interpreters: __all__.append('InterpreterPoolExecutor') @@ -54,16 +53,14 @@ def __getattr__(name): global ProcessPoolExecutor, ThreadPoolExecutor, InterpreterPoolExecutor if name == 'ProcessPoolExecutor': - from .process import ProcessPoolExecutor as pe - ProcessPoolExecutor = pe - return pe + from .process import ProcessPoolExecutor + return ProcessPoolExecutor if name == 'ThreadPoolExecutor': - from .thread import ThreadPoolExecutor as te - ThreadPoolExecutor = te - return te + from .thread import ThreadPoolExecutor + return ThreadPoolExecutor - if _have__interpreters and name == 'InterpreterPoolExecutor': + if _interpreters and name == 'InterpreterPoolExecutor': from .interpreter import InterpreterPoolExecutor return InterpreterPoolExecutor From 1946abd3dfce7f57be483a6f7bcddc79bfe7a8d6 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 01:56:24 +0900 Subject: [PATCH 07/13] Add test --- .../test_interpreter_pool.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/Lib/test/test_concurrent_futures/test_interpreter_pool.py b/Lib/test/test_concurrent_futures/test_interpreter_pool.py index 844dfdd6fc901c..494d1c72206ce4 100644 --- a/Lib/test/test_concurrent_futures/test_interpreter_pool.py +++ b/Lib/test/test_concurrent_futures/test_interpreter_pool.py @@ -2,7 +2,9 @@ import contextlib import io import os +import subprocess import sys +import textwrap import time import unittest from concurrent.futures.interpreter import BrokenInterpreterPool @@ -457,6 +459,38 @@ def test_free_reference(self): # Weak references don't cross between interpreters. raise unittest.SkipTest('not applicable') + def test_import_interpreter_pool_executor(self): + # Test the import behavior normally if _interpreters is unavailable. + code = textwrap.dedent(f""" + from concurrent import futures + import sys + # Set it to None to emulate the case when _interpreter is unavailable. + futures._interpreters = None + + try: + futures.InterpreterPoolExecutor + except AttributeError: + pass + else: + print('AttributeError not raised!', file=sys.stderr) + sys.exit(1) + + try: + from concurrent.futures import InterpreterPoolExecutor + except ImportError: + pass + else: + print('ImportError not raised!', file=sys.stderr) + sys.exit(1) + + + from concurrent.futures import * + """) + + cmd = [sys.executable, '-c', code] + p = subprocess.run(cmd, capture_output=True) + self.assertEqual(p.returncode, 0, p.stderr.decode()) + class AsyncioTest(InterpretersMixin, testasyncio_utils.TestCase): From f0c58ae9fad351b2e65ce7f880720384ecb42676 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 02:01:11 +0900 Subject: [PATCH 08/13] Fix test --- Lib/test/test_concurrent_futures/test_interpreter_pool.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_concurrent_futures/test_interpreter_pool.py b/Lib/test/test_concurrent_futures/test_interpreter_pool.py index 494d1c72206ce4..b81269ebb7ea9a 100644 --- a/Lib/test/test_concurrent_futures/test_interpreter_pool.py +++ b/Lib/test/test_concurrent_futures/test_interpreter_pool.py @@ -459,6 +459,7 @@ def test_free_reference(self): # Weak references don't cross between interpreters. raise unittest.SkipTest('not applicable') + @support.requires_subprocess() def test_import_interpreter_pool_executor(self): # Test the import behavior normally if _interpreters is unavailable. code = textwrap.dedent(f""" From 6a17c55bdb3da4c11e00c70aaf315c647969aa4d Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 02:08:02 +0900 Subject: [PATCH 09/13] Fix test --- Lib/test/test_concurrent_futures/test_interpreter_pool.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/test/test_concurrent_futures/test_interpreter_pool.py b/Lib/test/test_concurrent_futures/test_interpreter_pool.py index b81269ebb7ea9a..6427ee8fe7ee52 100644 --- a/Lib/test/test_concurrent_futures/test_interpreter_pool.py +++ b/Lib/test/test_concurrent_futures/test_interpreter_pool.py @@ -483,9 +483,6 @@ def test_import_interpreter_pool_executor(self): else: print('ImportError not raised!', file=sys.stderr) sys.exit(1) - - - from concurrent.futures import * """) cmd = [sys.executable, '-c', code] From 93366305bc4528d060211485cebf2ab06f9554dc Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 02:17:26 +0900 Subject: [PATCH 10/13] Better test --- Lib/test/test_concurrent_futures/test_interpreter_pool.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_concurrent_futures/test_interpreter_pool.py b/Lib/test/test_concurrent_futures/test_interpreter_pool.py index 6427ee8fe7ee52..0e8f43eeb6c51d 100644 --- a/Lib/test/test_concurrent_futures/test_interpreter_pool.py +++ b/Lib/test/test_concurrent_futures/test_interpreter_pool.py @@ -463,10 +463,10 @@ def test_free_reference(self): def test_import_interpreter_pool_executor(self): # Test the import behavior normally if _interpreters is unavailable. code = textwrap.dedent(f""" - from concurrent import futures import sys # Set it to None to emulate the case when _interpreter is unavailable. - futures._interpreters = None + sys.modules['_interpreters'] = None + from concurrent import futures try: futures.InterpreterPoolExecutor @@ -483,6 +483,8 @@ def test_import_interpreter_pool_executor(self): else: print('ImportError not raised!', file=sys.stderr) sys.exit(1) + + from concurrent.futures import * """) cmd = [sys.executable, '-c', code] From 36f0a0c0549c824113f5d6a4451544a7329da0a1 Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 22:02:14 +0900 Subject: [PATCH 11/13] Update Lib/concurrent/futures/__init__.py Co-authored-by: sobolevn --- Lib/concurrent/futures/__init__.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py index b6f0985058ea75..e717222cf98b32 100644 --- a/Lib/concurrent/futures/__init__.py +++ b/Lib/concurrent/futures/__init__.py @@ -34,12 +34,10 @@ ] -_interpreters = None - try: import _interpreters -except ModuleNotFoundError: - pass +except ImportError: + _interpreters = None if _interpreters: __all__.append('InterpreterPoolExecutor') From 0a9d5c81ee37ef67429ffcea40688e52d93aeeeb Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 22:06:04 +0900 Subject: [PATCH 12/13] Update Lib/test/test_concurrent_futures/test_interpreter_pool.py Co-authored-by: sobolevn --- Lib/test/test_concurrent_futures/test_interpreter_pool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_concurrent_futures/test_interpreter_pool.py b/Lib/test/test_concurrent_futures/test_interpreter_pool.py index 0e8f43eeb6c51d..764af2c3fb6ca9 100644 --- a/Lib/test/test_concurrent_futures/test_interpreter_pool.py +++ b/Lib/test/test_concurrent_futures/test_interpreter_pool.py @@ -462,7 +462,7 @@ def test_free_reference(self): @support.requires_subprocess() def test_import_interpreter_pool_executor(self): # Test the import behavior normally if _interpreters is unavailable. - code = textwrap.dedent(f""" + code = textwrap.dedent(""" import sys # Set it to None to emulate the case when _interpreter is unavailable. sys.modules['_interpreters'] = None From b2e6e698c26eeda350ec7e1ec0f9f55b6423bafc Mon Sep 17 00:00:00 2001 From: AN Long Date: Tue, 8 Jul 2025 22:06:37 +0900 Subject: [PATCH 13/13] Update test --- Lib/test/test_concurrent_futures/test_interpreter_pool.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/test_concurrent_futures/test_interpreter_pool.py b/Lib/test/test_concurrent_futures/test_interpreter_pool.py index 764af2c3fb6ca9..b10bbebd0984c4 100644 --- a/Lib/test/test_concurrent_futures/test_interpreter_pool.py +++ b/Lib/test/test_concurrent_futures/test_interpreter_pool.py @@ -485,11 +485,18 @@ def test_import_interpreter_pool_executor(self): sys.exit(1) from concurrent.futures import * + + if 'InterpreterPoolExecutor' in globals(): + print('InterpreterPoolExecutor should not be imported!', + file=sys.stderr) + sys.exit(1) """) cmd = [sys.executable, '-c', code] p = subprocess.run(cmd, capture_output=True) self.assertEqual(p.returncode, 0, p.stderr.decode()) + self.assertEqual(p.stdout.decode(), '') + self.assertEqual(p.stderr.decode(), '') class AsyncioTest(InterpretersMixin, testasyncio_utils.TestCase):