Skip to content

Commit 69114b4

Browse files
authored
Merge pull request #3249 from aatle/lazy-numpy
New approach to lazy loading of pygame submodules (`surfarray`, `sndarray`)
2 parents 75a1a11 + 0ef5f77 commit 69114b4

File tree

3 files changed

+64
-6
lines changed

3 files changed

+64
-6
lines changed

docs/reST/ref/sndarray.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ Each sample is an 8-bit or 16-bit integer, depending on the data format. A
2323
stereo sound file has two values per sample, while a mono sound file only has
2424
one.
2525

26+
.. versionchanged:: 2.5.6 sndarray module is lazily loaded to avoid an expensive NumPy import when unnecessary
27+
2628
.. function:: array
2729

2830
| :sl:`copy Sound samples into an array`

docs/reST/ref/surfarray.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ pixels from the surface and any changes performed to the array will make changes
3535
in the surface. As this last functions share memory with the surface, this one
3636
will be locked during the lifetime of the array.
3737

38+
.. versionchanged:: 2.5.6 surfarray module is lazily loaded to avoid an expensive NumPy import when unnecessary
39+
3840
.. function:: array2d
3941

4042
| :sl:`Copy pixels into a 2d array`

src_py/__init__.py

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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
302306
except (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+
305337
try:
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
307343
except (ImportError, OSError):
308344
surfarray = MissingModule("surfarray", urgent=0)
345+
else:
346+
surfarray = lazy_import("surfarray")
309347

310348
try:
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
312354
except (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

315361
try:
316362
import pygame._debug
@@ -361,13 +407,21 @@ def Window(title="pygame window", size=(640, 480), position=None, **kwargs): #
361407

362408

363409
def 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

Comments
 (0)