diff --git a/docs/reST/ref/sndarray.rst b/docs/reST/ref/sndarray.rst index 75dbd1b21b..caf2f81c0a 100644 --- a/docs/reST/ref/sndarray.rst +++ b/docs/reST/ref/sndarray.rst @@ -23,6 +23,8 @@ Each sample is an 8-bit or 16-bit integer, depending on the data format. A stereo sound file has two values per sample, while a mono sound file only has one. +.. versionchanged:: 2.5.4 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary + .. function:: array | :sl:`copy Sound samples into an array` diff --git a/docs/reST/ref/surfarray.rst b/docs/reST/ref/surfarray.rst index 9b56a0dfb8..5c0e3b5f19 100644 --- a/docs/reST/ref/surfarray.rst +++ b/docs/reST/ref/surfarray.rst @@ -35,6 +35,8 @@ pixels from the surface and any changes performed to the array will make changes in the surface. As this last functions share memory with the surface, this one will be locked during the lifetime of the array. +.. versionchanged:: 2.5.4 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary + .. function:: array2d | :sl:`Copy pixels into a 2d array` diff --git a/src_py/__init__.py b/src_py/__init__.py index c227a9e55e..ae30190cc1 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -300,15 +300,54 @@ def PixelArray(surface): # pylint: disable=unused-argument except (ImportError, OSError): scrap = MissingModule("scrap", urgent=0) +# Two lazily imported modules to avoid loading numpy unnecessarily + +from importlib.util import LazyLoader, find_spec, module_from_spec + + +def lazy_import(name): + """Lazily import a pygame module. + + See https://docs.python.org/3/library/importlib.html#implementing-lazy-imports + Only load the module upon its first attribute access. + + Lazily imported modules are directly referenced in packager_imports function. + """ + spec = find_spec("pygame." + name) + loader = LazyLoader(spec.loader) + spec.loader = loader + module = module_from_spec(spec) + sys.modules[spec.name] = module + loader.exec_module(module) + return module + + +# Check if numpy is available for surfarray and sndarray modules +numpy_missing = find_spec("numpy") is None + try: - import pygame.surfarray + if numpy_missing: + # Always fails here. Need the error message for MissingModule.reason + import numpy # pylint: disable=ungrouped-imports + # Check that module dependencies are not missing, or get error message + import pygame.pixelcopy # pylint: disable=ungrouped-imports except (ImportError, OSError): surfarray = MissingModule("surfarray", urgent=0) +else: + surfarray = lazy_import("surfarray") try: - import pygame.sndarray + if numpy_missing: + # Always fails here. Need the error message for MissingModule.reason + import numpy # pylint: disable=ungrouped-imports + # Check that module dependencies are not missing, or get error message + import pygame.mixer # pylint: disable=ungrouped-imports except (ImportError, OSError): sndarray = MissingModule("sndarray", urgent=0) +else: + sndarray = lazy_import("sndarray") + +del LazyLoader, find_spec, lazy_import, module_from_spec, numpy_missing try: import pygame._debug @@ -366,6 +405,10 @@ def packager_imports(): import pygame.macosx import pygame.colordict + # lazy imports + import pygame.surfarray + import pygame.sndarray + # make Rects pickleable