From 711c5e0e9178dd20550b0eb86b4d6f637ed1cea0 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sun, 1 Dec 2024 13:35:13 -0800 Subject: [PATCH 1/6] Lazy import implementation for surfarray, sndarray --- docs/reST/ref/sndarray.rst | 2 ++ docs/reST/ref/surfarray.rst | 2 ++ src_py/__init__.py | 38 ++++++++++++++++++++++++++++++------- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/docs/reST/ref/sndarray.rst b/docs/reST/ref/sndarray.rst index bc2899f614..733b49de1d 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.3 sndarray module is lazily loaded to avoid loading NumPy needlessly + .. function:: array | :sl:`copy Sound samples into an array` diff --git a/docs/reST/ref/surfarray.rst b/docs/reST/ref/surfarray.rst index 48b917fbfd..8df7ef88b3 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.3 surfarray module is lazily loaded to avoid loading NumPy needlessly + .. function:: array2d | :sl:`Copy pixels into a 2d array` diff --git a/src_py/__init__.py b/src_py/__init__.py index c227a9e55e..7faef2784e 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -300,16 +300,36 @@ def PixelArray(surface): # pylint: disable=unused-argument except (ImportError, OSError): scrap = MissingModule("scrap", urgent=0) -try: - import pygame.surfarray -except (ImportError, OSError): - surfarray = MissingModule("surfarray", urgent=0) +# Two lazily imported modules to avoid loading numpy unnecessarily -try: - import pygame.sndarray -except (ImportError, OSError): +from importlib.util import LazyLoader, find_spec, module_from_spec + + +def lazy_import(name): + """See https://docs.python.org/3/library/importlib.html#implementing-lazy-imports + + Only load modules upon their 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 + + +if find_spec("numpy") is not None: + surfarray = lazy_import("surfarray") + sndarray = lazy_import("sndarray") +else: + # Preserve MissingModule behavior when numpy is not installed + surfarray = MissingModule("surfarray", urgent=0) sndarray = MissingModule("sndarray", urgent=0) +del LazyLoader, find_spec, lazy_import, module_from_spec + try: import pygame._debug from pygame._debug import print_debug_info @@ -366,6 +386,10 @@ def packager_imports(): import pygame.macosx import pygame.colordict + # lazy imports + import pygame.surfarray + import pygame.sndarray + # make Rects pickleable From 2aeeee03adca027b34954178bd09fa157f92ee15 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sun, 1 Dec 2024 14:12:23 -0800 Subject: [PATCH 2/6] Handle all possible ImportErrors for lazy modules --- src_py/__init__.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src_py/__init__.py b/src_py/__init__.py index 7faef2784e..9ede1c893c 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -306,9 +306,11 @@ def PixelArray(surface): # pylint: disable=unused-argument def lazy_import(name): - """See https://docs.python.org/3/library/importlib.html#implementing-lazy-imports + """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. - Only load modules upon their first attribute access. Lazily imported modules are directly referenced in packager_imports function. """ spec = find_spec("pygame." + name) @@ -320,15 +322,22 @@ def lazy_import(name): return module -if find_spec("numpy") is not None: +numpy_exists = find_spec("numpy") is not None + +# Preserve MissingModule behavior when numpy is not installed +# and when their pygame module dependencies are unavailable + +if numpy_exists and not isinstance(pixelcopy, MissingModule): surfarray = lazy_import("surfarray") - sndarray = lazy_import("sndarray") else: - # Preserve MissingModule behavior when numpy is not installed surfarray = MissingModule("surfarray", urgent=0) + +if numpy_exists and not isinstance(mixer, MissingModule): + sndarray = lazy_import("sndarray") +else: sndarray = MissingModule("sndarray", urgent=0) -del LazyLoader, find_spec, lazy_import, module_from_spec +del LazyLoader, find_spec, lazy_import, module_from_spec, numpy_exists try: import pygame._debug From f2354320a0d6557d8af1e5b43115dd5376e2894e Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:22:24 -0800 Subject: [PATCH 3/6] Rewrite import checks to rely on try-except --- src_py/__init__.py | 30 +++++++++++++++++++----------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src_py/__init__.py b/src_py/__init__.py index 9ede1c893c..b49fa62580 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -322,22 +322,30 @@ def lazy_import(name): return module -numpy_exists = find_spec("numpy") is not None +# Check if numpy is available for surfarray and sndarray modules +numpy_missing = find_spec("numpy") is None -# Preserve MissingModule behavior when numpy is not installed -# and when their pygame module dependencies are unavailable - -if numpy_exists and not isinstance(pixelcopy, MissingModule): - surfarray = lazy_import("surfarray") -else: +try: + if numpy_missing: + # Always fails here. Need the error message for MissingModule.reason + import numpy + # Check that module dependencies are not missing, or get error message + import pygame.pixelcopy +except (ImportError, OSError): surfarray = MissingModule("surfarray", urgent=0) - -if numpy_exists and not isinstance(mixer, MissingModule): - sndarray = lazy_import("sndarray") else: + surfarray = lazy_import("surfarray") + +try: + if numpy_missing: + import numpy + import pygame.mixer +except (ImportError, OSError): sndarray = MissingModule("sndarray", urgent=0) +else: + sndarray = lazy_import("sndarray") -del LazyLoader, find_spec, lazy_import, module_from_spec, numpy_exists +del LazyLoader, find_spec, lazy_import, module_from_spec, numpy_missing try: import pygame._debug From a71dfb5a1d51a772f2088aaf25f7d74f39e0be26 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sun, 1 Dec 2024 15:44:07 -0800 Subject: [PATCH 4/6] Ignore a few pylint warnings --- src_py/__init__.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src_py/__init__.py b/src_py/__init__.py index b49fa62580..ae30190cc1 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -328,9 +328,9 @@ def lazy_import(name): try: if numpy_missing: # Always fails here. Need the error message for MissingModule.reason - import numpy + import numpy # pylint: disable=ungrouped-imports # Check that module dependencies are not missing, or get error message - import pygame.pixelcopy + import pygame.pixelcopy # pylint: disable=ungrouped-imports except (ImportError, OSError): surfarray = MissingModule("surfarray", urgent=0) else: @@ -338,8 +338,10 @@ def lazy_import(name): try: if numpy_missing: - import numpy - import pygame.mixer + # 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: From 85bb84d21df484ec5e886c62068999d9759a21ce Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sat, 7 Dec 2024 16:50:23 -0800 Subject: [PATCH 5/6] Improve docs clarity about avoiding numpy import --- docs/reST/ref/sndarray.rst | 2 +- docs/reST/ref/surfarray.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reST/ref/sndarray.rst b/docs/reST/ref/sndarray.rst index 733b49de1d..4fe3a11909 100644 --- a/docs/reST/ref/sndarray.rst +++ b/docs/reST/ref/sndarray.rst @@ -23,7 +23,7 @@ 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.3 sndarray module is lazily loaded to avoid loading NumPy needlessly +.. versionchanged:: 2.5.3 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary .. function:: array diff --git a/docs/reST/ref/surfarray.rst b/docs/reST/ref/surfarray.rst index 8df7ef88b3..a9370f24a3 100644 --- a/docs/reST/ref/surfarray.rst +++ b/docs/reST/ref/surfarray.rst @@ -35,7 +35,7 @@ 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.3 surfarray module is lazily loaded to avoid loading NumPy needlessly +.. versionchanged:: 2.5.3 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary .. function:: array2d From 66cdd815aec447c7a02056956c949cb00e912612 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Wed, 12 Mar 2025 23:30:51 -0700 Subject: [PATCH 6/6] Update versionchanged of lazy imports --- docs/reST/ref/sndarray.rst | 2 +- docs/reST/ref/surfarray.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reST/ref/sndarray.rst b/docs/reST/ref/sndarray.rst index 37b44d5c6d..caf2f81c0a 100644 --- a/docs/reST/ref/sndarray.rst +++ b/docs/reST/ref/sndarray.rst @@ -23,7 +23,7 @@ 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.3 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary +.. versionchanged:: 2.5.4 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary .. function:: array diff --git a/docs/reST/ref/surfarray.rst b/docs/reST/ref/surfarray.rst index 82fa4be8f5..5c0e3b5f19 100644 --- a/docs/reST/ref/surfarray.rst +++ b/docs/reST/ref/surfarray.rst @@ -35,7 +35,7 @@ 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.3 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary +.. versionchanged:: 2.5.4 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary .. function:: array2d