Bug report
Bug description:
Under 3.15, this doesn't work:
lazy from concurrent.futures import ProcessPoolExecutor def square(x): return x * x def main(): with ProcessPoolExecutor(max_workers=2) as executor: results = list(executor.map(square, [1, 2, 3, 4])) print(results) if __name__ == "__main__": main()
(I'm running with uv run --python=3.15 lazy.py) Without the lazy this works. If you use lazy import concurrent.futures it works. It also breaks in __lazy_modules__ form (which is how I ran into it). It breaks with ThreadPoolExecutor too.
The (rather poor) error message:
Traceback (most recent call last): File "/Users/henryfs/git/tmp/cpylazyproc/lazy.py", line 1, in <module> lazy from concurrent.futures import ProcessPoolExecutor ImportError: deferred import of 'concurrent.futures.ProcessPoolExecutor' raised an exception during resolution The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/Users/henryfs/git/tmp/cpylazyproc/lazy.py", line 15, in <module> main() ~~~~^^ File "/Users/henryfs/git/tmp/cpylazyproc/lazy.py", line 9, in main with ProcessPoolExecutor(max_workers=2) as executor: ^^^^^^^^^^^^^^^^^^^ ImportError: cannot import name 'ProcessPoolExecutor' from 'concurrent.futures' (/Users/henryfs/.local/share/uv/python/cpython-3.15.0b1-macos-aarch64-none/lib/python3.15/concurrent/futures/__init__.py)
I believe this is broken because concurrent.futures has a classic lazy import hack:
| def __getattr__(name): | |
| global ProcessPoolExecutor, ThreadPoolExecutor, InterpreterPoolExecutor | |
| if name == 'ProcessPoolExecutor': | |
| from .process import ProcessPoolExecutor | |
| return ProcessPoolExecutor | |
| if name == 'ThreadPoolExecutor': | |
| from .thread import ThreadPoolExecutor | |
| return ThreadPoolExecutor | |
| if _interpreters and name == 'InterpreterPoolExecutor': | |
| from .interpreter import InterpreterPoolExecutor | |
| return InterpreterPoolExecutor | |
| raise AttributeError(f"module {__name__!r} has no attribute {name!r}") |
I think there are two issues:
- Lazy imports ideally shouldn't break this hack, unless there's something fundamental that can't be fixed
- This hack should be replaced with lazy imports (now that
=nonehas been removed, it should be safe)
CPython versions tested on:
3.15
Operating systems tested on:
macOS