@@ -73,9 +73,13 @@ class MissingModule:
7373
7474 def __init__ (self , name , urgent = 0 ):
7575 self .name = name
76- exc_type , exc_msg = sys .exc_info ()[:2 ]
77- self .info = str (exc_msg )
78- self .reason = f"{ exc_type .__name__ } : { self .info } "
76+ exc_type , exc_msg , _ = sys .exc_info ()
77+ if exc_type is not None :
78+ self .info = str (exc_msg )
79+ self .reason = f"{ exc_type .__name__ } : { self .info } "
80+ else :
81+ self .info = "<no info>"
82+ self .reason = f"<no exception>: { self .info } "
7983 self .urgent = urgent
8084 if urgent :
8185 self .warn ()
@@ -302,15 +306,57 @@ def PixelArray(surface): # pylint: disable=unused-argument
302306except (ImportError , OSError ):
303307 scrap = MissingModule ("scrap" , urgent = 0 )
304308
309+ # Two lazily imported modules to avoid loading numpy unnecessarily
310+
311+ from importlib .util import LazyLoader , find_spec , module_from_spec
312+
313+
314+ def lazy_import (name ):
315+ """Lazily import a pygame module.
316+
317+ See https://docs.python.org/3/library/importlib.html#implementing-lazy-imports
318+ Only load the module upon its first attribute access.
319+
320+ Lazily imported modules are directly referenced in packager_imports function.
321+ """
322+ fullname = "pygame." + name
323+ spec = find_spec (fullname )
324+ if spec is None or spec .loader is None :
325+ return MissingModule (name , urgent = 0 )
326+ loader = LazyLoader (spec .loader )
327+ spec .loader = loader
328+ module = module_from_spec (spec )
329+ sys .modules [fullname ] = module
330+ loader .exec_module (module )
331+ return module
332+
333+
334+ # Check if numpy is available for surfarray and sndarray modules
335+ numpy_missing = find_spec ("numpy" ) is None
336+
305337try :
306- import pygame .surfarray
338+ if numpy_missing :
339+ # Always fails here. Need the error message for MissingModule.reason
340+ import numpy # pylint: disable=ungrouped-imports
341+ # Check that module dependencies are not missing, or get error message
342+ import pygame .pixelcopy # pylint: disable=ungrouped-imports
307343except (ImportError , OSError ):
308344 surfarray = MissingModule ("surfarray" , urgent = 0 )
345+ else :
346+ surfarray = lazy_import ("surfarray" )
309347
310348try :
311- import pygame .sndarray
349+ if numpy_missing :
350+ # Always fails here. Need the error message for MissingModule.reason
351+ import numpy # pylint: disable=ungrouped-imports
352+ # Check that module dependencies are not missing, or get error message
353+ import pygame .mixer # pylint: disable=ungrouped-imports
312354except (ImportError , OSError ):
313355 sndarray = MissingModule ("sndarray" , urgent = 0 )
356+ else :
357+ sndarray = lazy_import ("sndarray" )
358+
359+ del LazyLoader , find_spec , lazy_import , module_from_spec , numpy_missing
314360
315361try :
316362 import pygame ._debug
@@ -361,13 +407,21 @@ def Window(title="pygame window", size=(640, 480), position=None, **kwargs): #
361407
362408
363409def packager_imports ():
364- """some additional imports that py2app/py2exe will want to see"""
410+ """Some additional imports that py2app/py2exe will want to see.
411+
412+ This function is never executed.
413+ Some tools scan the source code for import statements.
414+ """
365415 import atexit
366416 import numpy
367417 import OpenGL .GL
368418 import pygame .macosx
369419 import pygame .colordict
370420
421+ # lazy imports
422+ import pygame .surfarray
423+ import pygame .sndarray
424+
371425
372426# make Rects pickleable
373427
0 commit comments