Skip to content

3.15: Lazy imports break with __getattr__ (concurrent.futures) #150579

@henryiii

Description

@henryiii

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 =none has been removed, it should be safe)

CPython versions tested on:

3.15

Operating systems tested on:

macOS

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    pendingThe issue will be closed if no feedback is providedtopic-lazy-importstype-bugAn unexpected behavior, bug, or error
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions