From efa96e856a67bcb39e6a13fd9deb724b4d8f0cc2 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:59:55 -0800 Subject: [PATCH 1/6] Implement lazy loading of surfarray, sndarray submodules --- src_py/__init__.py | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src_py/__init__.py b/src_py/__init__.py index c227a9e55e..e8caf2f7e3 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -259,7 +259,26 @@ def PixelArray(surface): # pylint: disable=unused-argument except (ImportError, OSError): transform = MissingModule("transform", urgent=1) + # lastly, the "optional" pygame modules + +_MissingModule = MissingModule + + +def __getattr__(name): + from importlib import import_module + + LAZY_MODULES = "surfarray", "sndarray" + if name not in LAZY_MODULES: + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + try: + module = import_module(f"{__name__}.{name}") + except (ImportError, OSError): + module = _MissingModule(name, urgent=0) + globals()[name] = module + return module + + if "PYGAME_FREETYPE" in os.environ: try: import pygame.ftfont as font @@ -300,16 +319,6 @@ 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) - -try: - import pygame.sndarray -except (ImportError, OSError): - sndarray = MissingModule("sndarray", urgent=0) - try: import pygame._debug from pygame._debug import print_debug_info From 146cf9095202598e8a082f7d742df76b5a9db70b Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:54:41 -0800 Subject: [PATCH 2/6] Fix bug in lazy loading --- src_py/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src_py/__init__.py b/src_py/__init__.py index e8caf2f7e3..868b9fba3c 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -72,6 +72,8 @@ class MissingModule: _NOT_IMPLEMENTED_ = True def __init__(self, name, urgent=0): + import sys + self.name = name exc_type, exc_msg = sys.exc_info()[:2] self.info = str(exc_msg) From 7fcd8e54c811f52330aca430c8521b48134dc496 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:27:45 -0800 Subject: [PATCH 3/6] Shush pylint --- src_py/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src_py/__init__.py b/src_py/__init__.py index 868b9fba3c..c058488167 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -72,7 +72,7 @@ class MissingModule: _NOT_IMPLEMENTED_ = True def __init__(self, name, urgent=0): - import sys + import sys # pylint: disable=reimported self.name = name exc_type, exc_msg = sys.exc_info()[:2] From df1f23d2b479707d7a38873dabc2cf63aa2558df Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Sun, 24 Nov 2024 23:51:55 -0800 Subject: [PATCH 4/6] Add explicit imports for lazily loaded modules --- src_py/__init__.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src_py/__init__.py b/src_py/__init__.py index c058488167..2f8ea4e058 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -377,6 +377,10 @@ def packager_imports(): import pygame.macosx import pygame.colordict + # lazily loaded pygame modules, just in case + import pygame.surfarray + import pygame.sndarray + # make Rects pickleable From e22b31f0681c14949c22109e93c59941c2ada0b7 Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Mon, 25 Nov 2024 00:24:51 -0800 Subject: [PATCH 5/6] Improve documentation of new lazy loading system --- docs/reST/ref/sndarray.rst | 2 ++ docs/reST/ref/surfarray.rst | 2 ++ src_py/__init__.py | 15 +++++++++++++++ 3 files changed, 19 insertions(+) diff --git a/docs/reST/ref/sndarray.rst b/docs/reST/ref/sndarray.rst index bc2899f614..95f0154fee 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 + .. 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..2fd3435693 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 + .. function:: array2d | :sl:`Copy pixels into a 2d array` diff --git a/src_py/__init__.py b/src_py/__init__.py index 2f8ea4e058..6676713fd3 100644 --- a/src_py/__init__.py +++ b/src_py/__init__.py @@ -264,17 +264,32 @@ def PixelArray(surface): # pylint: disable=unused-argument # lastly, the "optional" pygame modules +# Private, persisting alias for use in __getattr__ _MissingModule = MissingModule def __getattr__(name): + """Implementation of lazy loading for some optional pygame modules. + + The surfarray and sndarray submodules use numpy, so they are loaded + lazily to avoid a heavy numpy import if the modules are never used. + + The first access of a lazily loaded submodule loads it and sets it + as an attribute on the pygame module. Pygame itself doesn't import these modules. + If the first access is an attribute access and not an import, then __getattr__ is + invoked (as the attribute isn't set yet), which imports the module dynamically. + + All lazy submodules are directly referenced in the packager_imports function. + """ from importlib import import_module LAZY_MODULES = "surfarray", "sndarray" if name not in LAZY_MODULES: + # Normal behavior for attribute accesses that aren't lazy modules raise AttributeError(f"module '{__name__}' has no attribute '{name}'") try: module = import_module(f"{__name__}.{name}") + # A successful import automatically sets the module attribute on the package except (ImportError, OSError): module = _MissingModule(name, urgent=0) globals()[name] = module From e72bd1ec81b51d1ca9e8acba6a0cfe8d0dc75e3d Mon Sep 17 00:00:00 2001 From: aatle <168398276+aatle@users.noreply.github.com> Date: Wed, 27 Nov 2024 11:55:09 -0800 Subject: [PATCH 6/6] Add reasoning for lazy import change in module docs --- 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 95f0154fee..733b49de1d 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 +.. versionchanged:: 2.5.3 sndarray module is lazily loaded to avoid loading NumPy needlessly .. function:: array diff --git a/docs/reST/ref/surfarray.rst b/docs/reST/ref/surfarray.rst index 2fd3435693..8df7ef88b3 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 +.. versionchanged:: 2.5.3 surfarray module is lazily loaded to avoid loading NumPy needlessly .. function:: array2d