Skip to content

Commit 3b2d407

Browse files
committed
Revival of the backend loading refactor.
1 parent e85e53c commit 3b2d407

File tree

5 files changed

+135
-166
lines changed

5 files changed

+135
-166
lines changed

lib/matplotlib/backend_bases.py

+119-115
Original file line numberDiff line numberDiff line change
@@ -124,121 +124,6 @@ def get_registered_canvas_class(format):
124124
return backend_class
125125

126126

127-
class _Backend(object):
128-
# A backend can be defined by using the following pattern:
129-
#
130-
# @_Backend.export
131-
# class FooBackend(_Backend):
132-
# # override the attributes and methods documented below.
133-
134-
# The following attributes and methods must be overridden by subclasses.
135-
136-
# The `FigureCanvas` and `FigureManager` classes must be defined.
137-
FigureCanvas = None
138-
FigureManager = None
139-
140-
# The following methods must be left as None for non-interactive backends.
141-
# For interactive backends, `trigger_manager_draw` should be a function
142-
# taking a manager as argument and triggering a canvas draw, and `mainloop`
143-
# should be a function taking no argument and starting the backend main
144-
# loop.
145-
trigger_manager_draw = None
146-
mainloop = None
147-
148-
# The following methods will be automatically defined and exported, but
149-
# can be overridden.
150-
151-
@classmethod
152-
def new_figure_manager(cls, num, *args, **kwargs):
153-
"""Create a new figure manager instance.
154-
"""
155-
# This import needs to happen here due to circular imports.
156-
from matplotlib.figure import Figure
157-
fig_cls = kwargs.pop('FigureClass', Figure)
158-
fig = fig_cls(*args, **kwargs)
159-
return cls.new_figure_manager_given_figure(num, fig)
160-
161-
@classmethod
162-
def new_figure_manager_given_figure(cls, num, figure):
163-
"""Create a new figure manager instance for the given figure.
164-
"""
165-
canvas = cls.FigureCanvas(figure)
166-
manager = cls.FigureManager(canvas, num)
167-
return manager
168-
169-
@classmethod
170-
def draw_if_interactive(cls):
171-
if cls.trigger_manager_draw is not None and is_interactive():
172-
manager = Gcf.get_active()
173-
if manager:
174-
cls.trigger_manager_draw(manager)
175-
176-
@classmethod
177-
def show(cls, block=None):
178-
"""Show all figures.
179-
180-
`show` blocks by calling `mainloop` if *block* is ``True``, or if it
181-
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
182-
`interactive` mode.
183-
"""
184-
managers = Gcf.get_all_fig_managers()
185-
if not managers:
186-
return
187-
for manager in managers:
188-
# Emits a warning if the backend is non-interactive.
189-
manager.canvas.figure.show()
190-
if cls.mainloop is None:
191-
return
192-
if block is None:
193-
# Hack: Are we in IPython's pylab mode?
194-
from matplotlib import pyplot
195-
try:
196-
# IPython versions >= 0.10 tack the _needmain attribute onto
197-
# pyplot.show, and always set it to False, when in %pylab mode.
198-
ipython_pylab = not pyplot.show._needmain
199-
except AttributeError:
200-
ipython_pylab = False
201-
block = not ipython_pylab and not is_interactive()
202-
# TODO: The above is a hack to get the WebAgg backend working with
203-
# ipython's `%pylab` mode until proper integration is implemented.
204-
if get_backend() == "WebAgg":
205-
block = True
206-
if block:
207-
cls.mainloop()
208-
209-
# This method is the one actually exporting the required methods.
210-
211-
@staticmethod
212-
def export(cls):
213-
for name in ["FigureCanvas",
214-
"FigureManager",
215-
"new_figure_manager",
216-
"new_figure_manager_given_figure",
217-
"draw_if_interactive",
218-
"show"]:
219-
setattr(sys.modules[cls.__module__], name, getattr(cls, name))
220-
221-
# For back-compatibility, generate a shim `Show` class.
222-
223-
class Show(ShowBase):
224-
def mainloop(self):
225-
return cls.mainloop()
226-
227-
setattr(sys.modules[cls.__module__], "Show", Show)
228-
return cls
229-
230-
231-
class ShowBase(_Backend):
232-
"""
233-
Simple base class to generate a show() callable in backends.
234-
235-
Subclass must override mainloop() method.
236-
"""
237-
238-
def __call__(self, block=None):
239-
return self.show(block=block)
240-
241-
242127
class RendererBase(object):
243128
"""An abstract base class to handle drawing/rendering operations.
244129
@@ -3328,3 +3213,122 @@ def set_message(self, s):
33283213
Message text
33293214
"""
33303215
pass
3216+
3217+
3218+
class _Backend(object):
3219+
# A backend can be defined by using the following pattern:
3220+
#
3221+
# @_Backend.export
3222+
# class FooBackend(_Backend):
3223+
# # override the attributes and methods documented below.
3224+
3225+
# `backend_version` may be overridden by the subclass.
3226+
backend_version = "unknown"
3227+
3228+
# The `FigureCanvas` class must be defined.
3229+
FigureCanvas = None
3230+
3231+
# For interactive backends, the `FigureManager` class must be overridden.
3232+
FigureManager = FigureManagerBase
3233+
3234+
# The following methods must be left as None for non-interactive backends.
3235+
# For interactive backends, `trigger_manager_draw` should be a function
3236+
# taking a manager as argument and triggering a canvas draw, and `mainloop`
3237+
# should be a function taking no argument and starting the backend main
3238+
# loop.
3239+
trigger_manager_draw = None
3240+
mainloop = None
3241+
3242+
# The following methods will be automatically defined and exported, but
3243+
# can be overridden.
3244+
3245+
@classmethod
3246+
def new_figure_manager(cls, num, *args, **kwargs):
3247+
"""Create a new figure manager instance.
3248+
"""
3249+
# This import needs to happen here due to circular imports.
3250+
from matplotlib.figure import Figure
3251+
fig_cls = kwargs.pop('FigureClass', Figure)
3252+
fig = fig_cls(*args, **kwargs)
3253+
return cls.new_figure_manager_given_figure(num, fig)
3254+
3255+
@classmethod
3256+
def new_figure_manager_given_figure(cls, num, figure):
3257+
"""Create a new figure manager instance for the given figure.
3258+
"""
3259+
canvas = cls.FigureCanvas(figure)
3260+
manager = cls.FigureManager(canvas, num)
3261+
return manager
3262+
3263+
@classmethod
3264+
def draw_if_interactive(cls):
3265+
if cls.trigger_manager_draw is not None and is_interactive():
3266+
manager = Gcf.get_active()
3267+
if manager:
3268+
cls.trigger_manager_draw(manager)
3269+
3270+
@classmethod
3271+
def show(cls, block=None):
3272+
"""Show all figures.
3273+
3274+
`show` blocks by calling `mainloop` if *block* is ``True``, or if it
3275+
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
3276+
`interactive` mode.
3277+
"""
3278+
managers = Gcf.get_all_fig_managers()
3279+
if not managers:
3280+
return
3281+
for manager in managers:
3282+
# Emits a warning if the backend is non-interactive.
3283+
manager.canvas.figure.show()
3284+
if cls.mainloop is None:
3285+
return
3286+
if block is None:
3287+
# Hack: Are we in IPython's pylab mode?
3288+
from matplotlib import pyplot
3289+
try:
3290+
# IPython versions >= 0.10 tack the _needmain attribute onto
3291+
# pyplot.show, and always set it to False, when in %pylab mode.
3292+
ipython_pylab = not pyplot.show._needmain
3293+
except AttributeError:
3294+
ipython_pylab = False
3295+
block = not ipython_pylab and not is_interactive()
3296+
# TODO: The above is a hack to get the WebAgg backend working with
3297+
# ipython's `%pylab` mode until proper integration is implemented.
3298+
if get_backend() == "WebAgg":
3299+
block = True
3300+
if block:
3301+
cls.mainloop()
3302+
3303+
# This method is the one actually exporting the required methods.
3304+
3305+
@staticmethod
3306+
def export(cls):
3307+
for name in ["backend_version",
3308+
"FigureCanvas",
3309+
"FigureManager",
3310+
"new_figure_manager",
3311+
"new_figure_manager_given_figure",
3312+
"draw_if_interactive",
3313+
"show"]:
3314+
setattr(sys.modules[cls.__module__], name, getattr(cls, name))
3315+
3316+
# For back-compatibility, generate a shim `Show` class.
3317+
3318+
class Show(ShowBase):
3319+
def mainloop(self):
3320+
return cls.mainloop()
3321+
3322+
setattr(sys.modules[cls.__module__], "Show", Show)
3323+
return cls
3324+
3325+
3326+
class ShowBase(_Backend):
3327+
"""
3328+
Simple base class to generate a show() callable in backends.
3329+
3330+
Subclass must override mainloop() method.
3331+
"""
3332+
3333+
def __call__(self, block=None):
3334+
return self.show(block=block)

lib/matplotlib/backends/__init__.py

+12-42
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import importlib
12
import inspect
23
import logging
34
import traceback
4-
import warnings
55

66
import matplotlib
7+
from matplotlib.backend_bases import _Backend
78

89
_log = logging.getLogger(__name__)
910

@@ -43,50 +44,19 @@ def pylab_setup(name=None):
4344
'''
4445
# Import the requested backend into a generic module object
4546
if name is None:
46-
# validates, to match all_backends
4747
name = matplotlib.get_backend()
48-
if name.startswith('module://'):
49-
backend_name = name[9:]
50-
else:
51-
backend_name = 'backend_' + name
52-
backend_name = backend_name.lower() # until we banish mixed case
53-
backend_name = 'matplotlib.backends.%s' % backend_name.lower()
54-
55-
# the last argument is specifies whether to use absolute or relative
56-
# imports. 0 means only perform absolute imports.
57-
backend_mod = __import__(backend_name, globals(), locals(),
58-
[backend_name], 0)
59-
60-
# Things we pull in from all backends
61-
new_figure_manager = backend_mod.new_figure_manager
62-
63-
# image backends like pdf, agg or svg do not need to do anything
64-
# for "show" or "draw_if_interactive", so if they are not defined
65-
# by the backend, just do nothing
66-
def do_nothing_show(*args, **kwargs):
67-
frame = inspect.currentframe()
68-
fname = frame.f_back.f_code.co_filename
69-
if fname in ('<stdin>', '<ipython console>'):
70-
warnings.warn("""
71-
Your currently selected backend, '%s' does not support show().
72-
Please select a GUI backend in your matplotlibrc file ('%s')
73-
or with matplotlib.use()""" %
74-
(name, matplotlib.matplotlib_fname()))
75-
76-
def do_nothing(*args, **kwargs):
77-
pass
78-
79-
backend_version = getattr(backend_mod, 'backend_version', 'unknown')
80-
81-
show = getattr(backend_mod, 'show', do_nothing_show)
82-
83-
draw_if_interactive = getattr(backend_mod, 'draw_if_interactive',
84-
do_nothing)
85-
86-
_log.debug('backend %s version %s', name, backend_version)
48+
backend_name = (name[9:] if name.startswith("module://")
49+
else "matplotlib.backends.backend_{}".format(name.lower()))
50+
backend_mod = importlib.import_module(backend_name)
51+
Backend = type("Backend", (_Backend,), vars(backend_mod))
52+
_log.debug('backend %s version %s', name, Backend.backend_version)
8753

8854
# need to keep a global reference to the backend for compatibility
8955
# reasons. See https://github.com/matplotlib/matplotlib/issues/6092
9056
global backend
9157
backend = name
92-
return backend_mod, new_figure_manager, draw_if_interactive, show
58+
59+
return (backend_mod,
60+
Backend.new_figure_manager,
61+
Backend.draw_if_interactive,
62+
Backend.show)

lib/matplotlib/backends/backend_pdf.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -2582,11 +2582,9 @@ def print_pdf(self, filename, *,
25822582
file.close()
25832583

25842584

2585-
class FigureManagerPdf(FigureManagerBase):
2586-
pass
2585+
FigureManagerPdf = FigureManagerBase
25872586

25882587

25892588
@_Backend.export
25902589
class _BackendPdf(_Backend):
25912590
FigureCanvas = FigureCanvasPdf
2592-
FigureManager = FigureManagerPdf

lib/matplotlib/backends/backend_ps.py

+1-3
Original file line numberDiff line numberDiff line change
@@ -1699,8 +1699,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False):
16991699
shutil.move(epsfile, tmpfile)
17001700

17011701

1702-
class FigureManagerPS(FigureManagerBase):
1703-
pass
1702+
FigureManagerPS = FigureManagerBase
17041703

17051704

17061705
# The following Python dictionary psDefs contains the entries for the
@@ -1746,4 +1745,3 @@ class FigureManagerPS(FigureManagerBase):
17461745
@_Backend.export
17471746
class _BackendPS(_Backend):
17481747
FigureCanvas = FigureCanvasPS
1749-
FigureManager = FigureManagerPS

lib/matplotlib/backends/backend_svg.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -1213,8 +1213,8 @@ def _print_svg(
12131213
def get_default_filetype(self):
12141214
return 'svg'
12151215

1216-
class FigureManagerSVG(FigureManagerBase):
1217-
pass
1216+
1217+
FigureManagerSVG = FigureManagerBase
12181218

12191219

12201220
svgProlog = """\
@@ -1228,4 +1228,3 @@ class FigureManagerSVG(FigureManagerBase):
12281228
@_Backend.export
12291229
class _BackendSVG(_Backend):
12301230
FigureCanvas = FigureCanvasSVG
1231-
FigureManager = FigureManagerSVG

0 commit comments

Comments
 (0)