Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2c73aba

Browse files
committed
Rework backend loading.
This is preliminary work towards fixing the runtime detection of default backend problem (the first step being to make it easier to check whether a backend *can* be loaded). No publically visible API is changed. Backend loading now defines default versions of `backend_version`, `draw_if_interactive`, and `show` using the same inheritance strategy as builtin backends do. For non-interactive backends (which don't override `mainloop`), restore the default implementation of `show()` that prints a warning when run from a console (the backend refactor accidentally removed this error message as it provided a silent default `show()`). The `_Backend` class had to be moved to the end of the module as `FigureManagerBase` needs to be defined first. The `ShowBase` class had to be moved even after that as it depends on the `_Backend` class.
1 parent 884060a commit 2c73aba

File tree

5 files changed

+158
-168
lines changed

5 files changed

+158
-168
lines changed

lib/matplotlib/backend_bases.py

Lines changed: 127 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
from contextlib import contextmanager
4242
from functools import partial
4343
import importlib
44+
import inspect
4445
import io
4546
import os
4647
import sys
@@ -126,120 +127,6 @@ def get_registered_canvas_class(format):
126127
return backend_class
127128

128129

129-
class _Backend(object):
130-
# A backend can be defined by using the following pattern:
131-
#
132-
# @_Backend.export
133-
# class FooBackend(_Backend):
134-
# # override the attributes and methods documented below.
135-
136-
# The following attributes and methods must be overridden by subclasses.
137-
138-
# The `FigureCanvas` and `FigureManager` classes must be defined.
139-
FigureCanvas = None
140-
FigureManager = None
141-
142-
# The following methods must be left as None for non-interactive backends.
143-
# For interactive backends, `trigger_manager_draw` should be a function
144-
# taking a manager as argument and triggering a canvas draw, and `mainloop`
145-
# should be a function taking no argument and starting the backend main
146-
# loop.
147-
trigger_manager_draw = None
148-
mainloop = None
149-
150-
# The following methods will be automatically defined and exported, but
151-
# can be overridden.
152-
153-
@classmethod
154-
def new_figure_manager(cls, num, *args, **kwargs):
155-
"""Create a new figure manager instance.
156-
"""
157-
# This import needs to happen here due to circular imports.
158-
from matplotlib.figure import Figure
159-
fig_cls = kwargs.pop('FigureClass', Figure)
160-
fig = fig_cls(*args, **kwargs)
161-
return cls.new_figure_manager_given_figure(num, fig)
162-
163-
@classmethod
164-
def new_figure_manager_given_figure(cls, num, figure):
165-
"""Create a new figure manager instance for the given figure.
166-
"""
167-
canvas = cls.FigureCanvas(figure)
168-
manager = cls.FigureManager(canvas, num)
169-
return manager
170-
171-
@classmethod
172-
def draw_if_interactive(cls):
173-
if cls.trigger_manager_draw is not None and is_interactive():
174-
manager = Gcf.get_active()
175-
if manager:
176-
cls.trigger_manager_draw(manager)
177-
178-
@classmethod
179-
def show(cls, block=None):
180-
"""Show all figures.
181-
182-
`show` blocks by calling `mainloop` if *block* is ``True``, or if it
183-
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
184-
`interactive` mode.
185-
"""
186-
if cls.mainloop is None:
187-
return
188-
managers = Gcf.get_all_fig_managers()
189-
if not managers:
190-
return
191-
for manager in managers:
192-
manager.show()
193-
if block is None:
194-
# Hack: Are we in IPython's pylab mode?
195-
from matplotlib import pyplot
196-
try:
197-
# IPython versions >= 0.10 tack the _needmain attribute onto
198-
# pyplot.show, and always set it to False, when in %pylab mode.
199-
ipython_pylab = not pyplot.show._needmain
200-
except AttributeError:
201-
ipython_pylab = False
202-
block = not ipython_pylab and not is_interactive()
203-
# TODO: The above is a hack to get the WebAgg backend working with
204-
# ipython's `%pylab` mode until proper integration is implemented.
205-
if get_backend() == "WebAgg":
206-
block = True
207-
if block:
208-
cls.mainloop()
209-
210-
# This method is the one actually exporting the required methods.
211-
212-
@staticmethod
213-
def export(cls):
214-
for name in ["FigureCanvas",
215-
"FigureManager",
216-
"new_figure_manager",
217-
"new_figure_manager_given_figure",
218-
"draw_if_interactive",
219-
"show"]:
220-
setattr(sys.modules[cls.__module__], name, getattr(cls, name))
221-
222-
# For back-compatibility, generate a shim `Show` class.
223-
224-
class Show(ShowBase):
225-
def mainloop(self):
226-
return cls.mainloop()
227-
228-
setattr(sys.modules[cls.__module__], "Show", Show)
229-
return cls
230-
231-
232-
class ShowBase(_Backend):
233-
"""
234-
Simple base class to generate a show() callable in backends.
235-
236-
Subclass must override mainloop() method.
237-
"""
238-
239-
def __call__(self, block=None):
240-
return self.show(block=block)
241-
242-
243130
class RendererBase(object):
244131
"""An abstract base class to handle drawing/rendering operations.
245132
@@ -3364,3 +3251,129 @@ def set_message(self, s):
33643251
Message text
33653252
"""
33663253
pass
3254+
3255+
3256+
class _Backend(object):
3257+
# A backend can be defined by using the following pattern:
3258+
#
3259+
# @_Backend.export
3260+
# class FooBackend(_Backend):
3261+
# # override the attributes and methods documented below.
3262+
3263+
# May be overridden by the subclass.
3264+
backend_version = "unknown"
3265+
# The `FigureCanvas` class must be overridden.
3266+
FigureCanvas = None
3267+
# For interactive backends, the `FigureManager` class must be overridden.
3268+
FigureManager = FigureManagerBase
3269+
# The following methods must be left as None for non-interactive backends.
3270+
# For interactive backends, `trigger_manager_draw` should be a function
3271+
# taking a manager as argument and triggering a canvas draw, and `mainloop`
3272+
# should be a function taking no argument and starting the backend main
3273+
# loop.
3274+
trigger_manager_draw = None
3275+
mainloop = None
3276+
3277+
# The following methods will be automatically defined and exported, but
3278+
# can be overridden.
3279+
3280+
@classmethod
3281+
def new_figure_manager(cls, num, *args, **kwargs):
3282+
"""Create a new figure manager instance.
3283+
"""
3284+
# This import needs to happen here due to circular imports.
3285+
from matplotlib.figure import Figure
3286+
fig_cls = kwargs.pop('FigureClass', Figure)
3287+
fig = fig_cls(*args, **kwargs)
3288+
return cls.new_figure_manager_given_figure(num, fig)
3289+
3290+
@classmethod
3291+
def new_figure_manager_given_figure(cls, num, figure):
3292+
"""Create a new figure manager instance for the given figure.
3293+
"""
3294+
canvas = cls.FigureCanvas(figure)
3295+
manager = cls.FigureManager(canvas, num)
3296+
return manager
3297+
3298+
@classmethod
3299+
def draw_if_interactive(cls):
3300+
if cls.trigger_manager_draw is not None and is_interactive():
3301+
manager = Gcf.get_active()
3302+
if manager:
3303+
cls.trigger_manager_draw(manager)
3304+
3305+
@classmethod
3306+
def show(cls, block=None):
3307+
"""Show all figures.
3308+
3309+
`show` blocks by calling `mainloop` if *block* is ``True``, or if it
3310+
is ``None`` and we are neither in IPython's ``%pylab`` mode, nor in
3311+
`interactive` mode.
3312+
"""
3313+
if cls.mainloop is None:
3314+
frame = inspect.currentframe()
3315+
while frame:
3316+
if frame.f_code.co_filename in [
3317+
"<stdin>", "<ipython console>"]:
3318+
warnings.warn("""\
3319+
Your currently selected backend does not support show().
3320+
Please select a GUI backend in your matplotlibrc file ('{}')
3321+
or with matplotlib.use()""".format(matplotlib.matplotlib_fname()))
3322+
break
3323+
else:
3324+
frame = frame.f_back
3325+
return
3326+
managers = Gcf.get_all_fig_managers()
3327+
if not managers:
3328+
return
3329+
for manager in managers:
3330+
manager.show()
3331+
if block is None:
3332+
# Hack: Are we in IPython's pylab mode?
3333+
from matplotlib import pyplot
3334+
try:
3335+
# IPython versions >= 0.10 tack the _needmain attribute onto
3336+
# pyplot.show, and always set it to False, when in %pylab mode.
3337+
ipython_pylab = not pyplot.show._needmain
3338+
except AttributeError:
3339+
ipython_pylab = False
3340+
block = not ipython_pylab and not is_interactive()
3341+
# TODO: The above is a hack to get the WebAgg backend working with
3342+
# ipython's `%pylab` mode until proper integration is implemented.
3343+
if get_backend() == "WebAgg":
3344+
block = True
3345+
if block:
3346+
cls.mainloop()
3347+
3348+
# This method is the one actually exporting the required methods.
3349+
3350+
@staticmethod
3351+
def export(cls):
3352+
for name in ["backend_version",
3353+
"FigureCanvas",
3354+
"FigureManager",
3355+
"new_figure_manager",
3356+
"new_figure_manager_given_figure",
3357+
"draw_if_interactive",
3358+
"show"]:
3359+
setattr(sys.modules[cls.__module__], name, getattr(cls, name))
3360+
3361+
# For back-compatibility, generate a shim `Show` class.
3362+
3363+
class Show(ShowBase):
3364+
def mainloop(self):
3365+
return cls.mainloop()
3366+
3367+
setattr(sys.modules[cls.__module__], "Show", Show)
3368+
return cls
3369+
3370+
3371+
class ShowBase(_Backend):
3372+
"""
3373+
Simple base class to generate a show() callable in backends.
3374+
3375+
Subclass must override mainloop() method.
3376+
"""
3377+
3378+
def __call__(self, block=None):
3379+
return self.show(block=block)

lib/matplotlib/backends/__init__.py

Lines changed: 27 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
import six
55

6-
import matplotlib
7-
import inspect
8-
import traceback
9-
import warnings
6+
import importlib
107
import logging
8+
import traceback
9+
10+
import matplotlib
11+
from matplotlib.backend_bases import _Backend
12+
1113

1214
_log = logging.getLogger(__name__)
1315

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

9260
# need to keep a global reference to the backend for compatibility
9361
# reasons. See https://github.com/matplotlib/matplotlib/issues/6092
9462
global backend
9563
backend = name
96-
return backend_mod, new_figure_manager, draw_if_interactive, show
64+
65+
# We want to get functions out of a class namespace and call them *without
66+
# the first argument being an instance of the class*. This works directly
67+
# on Py3. On Py2, we need to remove the check that the first argument be
68+
# an instance of the class. The only relevant case is if `.im_self` is
69+
# None, in which case we need to use `.im_func` (if we have a bound method
70+
# (e.g. a classmethod), everything is fine).
71+
def _dont_check_first_arg(func):
72+
return (func.im_func if getattr(func, "im_self", 0) is None
73+
else func)
74+
75+
return (backend_mod,
76+
_dont_check_first_arg(Backend.new_figure_manager),
77+
_dont_check_first_arg(Backend.draw_if_interactive),
78+
_dont_check_first_arg(Backend.show))

lib/matplotlib/backends/backend_pdf.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2594,11 +2594,9 @@ def print_pdf(self, filename, **kwargs):
25942594
file.close()
25952595

25962596

2597-
class FigureManagerPdf(FigureManagerBase):
2598-
pass
2597+
FigureManagerPdf = FigureManagerBase
25992598

26002599

26012600
@_Backend.export
26022601
class _BackendPdf(_Backend):
26032602
FigureCanvas = FigureCanvasPdf
2604-
FigureManager = FigureManagerPdf

lib/matplotlib/backends/backend_ps.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1721,8 +1721,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False):
17211721
shutil.move(epsfile, tmpfile)
17221722

17231723

1724-
class FigureManagerPS(FigureManagerBase):
1725-
pass
1724+
FigureManagerPS = FigureManagerBase
17261725

17271726

17281727
# The following Python dictionary psDefs contains the entries for the
@@ -1768,4 +1767,3 @@ class FigureManagerPS(FigureManagerBase):
17681767
@_Backend.export
17691768
class _BackendPS(_Backend):
17701769
FigureCanvas = FigureCanvasPS
1771-
FigureManager = FigureManagerPS

lib/matplotlib/backends/backend_svg.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1252,8 +1252,8 @@ def _print_svg(self, filename, svgwriter, **kwargs):
12521252
def get_default_filetype(self):
12531253
return 'svg'
12541254

1255-
class FigureManagerSVG(FigureManagerBase):
1256-
pass
1255+
1256+
FigureManagerSVG = FigureManagerBase
12571257

12581258

12591259
svgProlog = """\
@@ -1267,4 +1267,3 @@ class FigureManagerSVG(FigureManagerBase):
12671267
@_Backend.export
12681268
class _BackendSVG(_Backend):
12691269
FigureCanvas = FigureCanvasSVG
1270-
FigureManager = FigureManagerSVG

0 commit comments

Comments
 (0)