From 4410cc11a2806aecaad90d6c885a56e94d42184b Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 3 Nov 2025 10:19:22 +0100 Subject: [PATCH 1/2] defer import --- ultraplot/__init__.py | 4 ++-- ultraplot/axes/plot.py | 20 +++++++------------- ultraplot/ui.py | 22 ++++++++++++++++++++-- 3 files changed, 29 insertions(+), 17 deletions(-) diff --git a/ultraplot/__init__.py b/ultraplot/__init__.py index 2a2db3bd1..83058fe46 100644 --- a/ultraplot/__init__.py +++ b/ultraplot/__init__.py @@ -16,8 +16,8 @@ from . import internals, externals, tests # noqa: F401 from .internals.benchmarks import _benchmark -with _benchmark("pyplot"): - from matplotlib import pyplot # noqa: F401 +# Defer pyplot import - it's the biggest import time bottleneck +# It will be imported lazily in ui.py when needed with _benchmark("cartopy"): try: import cartopy # noqa: F401 diff --git a/ultraplot/axes/plot.py b/ultraplot/axes/plot.py index 9364fd0bd..7099ef31e 100644 --- a/ultraplot/axes/plot.py +++ b/ultraplot/axes/plot.py @@ -28,7 +28,6 @@ import matplotlib.lines as mlines import matplotlib.patches as mpatches import matplotlib.ticker as mticker -import matplotlib.pyplot as mplt import matplotlib as mpl from packaging import version import numpy as np @@ -387,25 +386,20 @@ docstring._snippet_manager["plot.cycle"] = _cycle_docstring docstring._snippet_manager["plot.cmap_norm"] = _cmap_norm_docstring + +# Log plot docstrings - built without importing pyplot _log_doc = """ Plot {kind} UltraPlot is optimized for visualizing logarithmic scales by default. For cases with large differences in magnitude, we recommend setting `rc["formatter.log"] = True` to enhance axis label formatting. -{matplotlib_doc} -""" - -docstring._snippet_manager["plot.loglog"] = _log_doc.format( - kind="loglog", matplotlib_doc=mplt.loglog.__doc__ -) -docstring._snippet_manager["plot.semilogy"] = _log_doc.format( - kind="semilogy", matplotlib_doc=mplt.semilogy.__doc__ -) +See matplotlib.pyplot.{kind} for more details. +""" -docstring._snippet_manager["plot.semilogx"] = _log_doc.format( - kind="semilogx", matplotlib_doc=mplt.semilogx.__doc__ -) +docstring._snippet_manager["plot.loglog"] = _log_doc.format(kind="loglog") +docstring._snippet_manager["plot.semilogy"] = _log_doc.format(kind="semilogy") +docstring._snippet_manager["plot.semilogx"] = _log_doc.format(kind="semilogx") # Levels docstrings # NOTE: In some functions we only need some components diff --git a/ultraplot/ui.py b/ultraplot/ui.py index 7fb66334e..f19f96d7b 100644 --- a/ultraplot/ui.py +++ b/ultraplot/ui.py @@ -2,8 +2,6 @@ """ The starting point for creating ultraplot figures. """ -import matplotlib.pyplot as plt - from . import axes as paxes from . import figure as pfigure from . import gridspec as pgridspec @@ -22,6 +20,19 @@ "isinteractive", ] +# Lazy pyplot import +_plt = None + + +def _get_pyplot(): + """Get matplotlib.pyplot, importing it lazily on first use.""" + global _plt + if _plt is None: + import matplotlib.pyplot as plt + + _plt = plt + return _plt + # Docstrings _pyplot_docstring = """ @@ -58,6 +69,7 @@ def show(*args, **kwargs): *args, **kwargs Passed to `matplotlib.pyplot.show`. """ + plt = _get_pyplot() return plt.show(*args, **kwargs) @@ -72,6 +84,7 @@ def close(*args, **kwargs): *args, **kwargs Passed to `matplotlib.pyplot.close`. """ + plt = _get_pyplot() return plt.close(*args, **kwargs) @@ -86,6 +99,7 @@ def switch_backend(*args, **kwargs): *args, **kwargs Passed to `matplotlib.pyplot.switch_backend`. """ + plt = _get_pyplot() return plt.switch_backend(*args, **kwargs) @@ -95,6 +109,7 @@ def ion(): Call `matplotlib.pyplot.ion`. %(ui.pyplot)s """ + plt = _get_pyplot() return plt.ion() @@ -104,6 +119,7 @@ def ioff(): Call `matplotlib.pyplot.ioff`. %(ui.pyplot)s """ + plt = _get_pyplot() return plt.ioff() @@ -113,6 +129,7 @@ def isinteractive(): Call `matplotlib.pyplot.isinteractive`. %(ui.pyplot)s """ + plt = _get_pyplot() return plt.isinteractive() @@ -141,6 +158,7 @@ def figure(**kwargs): matplotlib.figure.Figure """ _parse_figsize(kwargs) + plt = _get_pyplot() return plt.figure(FigureClass=pfigure.Figure, **kwargs) From d557f319ae471ba7e74ba36fe0e419eb5f3263d3 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Mon, 3 Nov 2025 10:37:21 +0100 Subject: [PATCH 2/2] defer import to ensure pyplot is avaible if needed --- ultraplot/__init__.py | 10 ++++++++++ ultraplot/ui.py | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/ultraplot/__init__.py b/ultraplot/__init__.py index 83058fe46..8d5975454 100644 --- a/ultraplot/__init__.py +++ b/ultraplot/__init__.py @@ -115,3 +115,13 @@ if rc["ultraplot.check_for_latest_version"]: check_for_update("ultraplot") + + +# Lazy pyplot access for backward compatibility +def __getattr__(name): + """Lazy load pyplot when accessed as ultraplot.pyplot.""" + if name == "pyplot": + from .ui import _get_pyplot + + return _get_pyplot() + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") diff --git a/ultraplot/ui.py b/ultraplot/ui.py index f19f96d7b..e1f8b0891 100644 --- a/ultraplot/ui.py +++ b/ultraplot/ui.py @@ -18,6 +18,8 @@ "ion", "ioff", "isinteractive", + # Note: pyplot is NOT in __all__ to prevent eager loading during star import + # It's accessible via __getattr__ for lazy loading: import ultraplot as uplt; uplt.pyplot ] # Lazy pyplot import @@ -34,6 +36,14 @@ def _get_pyplot(): return _plt +# Make pyplot accessible at module level +def __getattr__(name): + """Lazy load pyplot when accessed as module attribute.""" + if name == "pyplot": + return _get_pyplot() + raise AttributeError(f"module '{__name__}' has no attribute '{name}'") + + # Docstrings _pyplot_docstring = """ This is included so you don't have to import `~matplotlib.pyplot`.