-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Refactor backend loading #9551
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor backend loading #9551
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Non-interactive FigureManager classes are now aliases of FigureManagerBase | ||
`````````````````````````````````````````````````````````````````````````` | ||
|
||
The `FigureManagerPdf`, `FigureManagerPS`, and `FigureManagerSVG` classes, | ||
which were previously empty subclasses of `FigureManagerBase` (i.e., not | ||
adding or overriding any attribute or method), are now direct aliases for | ||
`FigureManagerBase`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,9 @@ | ||
import inspect | ||
import importlib | ||
import logging | ||
import traceback | ||
import warnings | ||
|
||
import matplotlib | ||
from matplotlib.backend_bases import _Backend | ||
|
||
_log = logging.getLogger(__name__) | ||
|
||
|
@@ -15,10 +15,11 @@ | |
|
||
|
||
def pylab_setup(name=None): | ||
'''return new_figure_manager, draw_if_interactive and show for pyplot | ||
""" | ||
Return new_figure_manager, draw_if_interactive and show for pyplot. | ||
|
||
This provides the backend-specific functions that are used by | ||
pyplot to abstract away the difference between interactive backends. | ||
This provides the backend-specific functions that are used by pyplot to | ||
abstract away the difference between backends. | ||
|
||
Parameters | ||
---------- | ||
|
@@ -39,54 +40,25 @@ def pylab_setup(name=None): | |
|
||
show : function | ||
Show (and possibly block) any unshown figures. | ||
|
||
''' | ||
# Import the requested backend into a generic module object | ||
""" | ||
# Import the requested backend into a generic module object. | ||
if name is None: | ||
# validates, to match all_backends | ||
name = matplotlib.get_backend() | ||
if name.startswith('module://'): | ||
backend_name = name[9:] | ||
else: | ||
backend_name = 'backend_' + name | ||
backend_name = backend_name.lower() # until we banish mixed case | ||
backend_name = 'matplotlib.backends.%s' % backend_name.lower() | ||
|
||
# the last argument is specifies whether to use absolute or relative | ||
# imports. 0 means only perform absolute imports. | ||
backend_mod = __import__(backend_name, globals(), locals(), | ||
[backend_name], 0) | ||
|
||
# Things we pull in from all backends | ||
new_figure_manager = backend_mod.new_figure_manager | ||
|
||
# image backends like pdf, agg or svg do not need to do anything | ||
# for "show" or "draw_if_interactive", so if they are not defined | ||
# by the backend, just do nothing | ||
def do_nothing_show(*args, **kwargs): | ||
frame = inspect.currentframe() | ||
fname = frame.f_back.f_code.co_filename | ||
if fname in ('<stdin>', '<ipython console>'): | ||
warnings.warn(""" | ||
Your currently selected backend, '%s' does not support show(). | ||
Please select a GUI backend in your matplotlibrc file ('%s') | ||
or with matplotlib.use()""" % | ||
(name, matplotlib.matplotlib_fname()), stacklevel=2) | ||
|
||
def do_nothing(*args, **kwargs): | ||
pass | ||
|
||
backend_version = getattr(backend_mod, 'backend_version', 'unknown') | ||
|
||
show = getattr(backend_mod, 'show', do_nothing_show) | ||
|
||
draw_if_interactive = getattr(backend_mod, 'draw_if_interactive', | ||
do_nothing) | ||
|
||
_log.debug('backend %s version %s', name, backend_version) | ||
|
||
# need to keep a global reference to the backend for compatibility | ||
# reasons. See https://github.com/matplotlib/matplotlib/issues/6092 | ||
backend_name = (name[9:] if name.startswith("module://") | ||
else "matplotlib.backends.backend_{}".format(name.lower())) | ||
backend_mod = importlib.import_module(backend_name) | ||
# Create a local Backend class whose body corresponds to the contents of | ||
# the backend module. This allows the Backend class to fill in the missing | ||
# methods through inheritance. | ||
Backend = type("Backend", (_Backend,), vars(backend_mod)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So we create the class, use the inheritance to fill in the missing functions and then return class level functions (which 'forget' they were part of the class)? That is quite clever. To check, if a backend is already using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Indeed, backends that were using |
||
|
||
# Need to keep a global reference to the backend for compatibility reasons. | ||
# See https://github.com/matplotlib/matplotlib/issues/6092 | ||
global backend | ||
backend = name | ||
return backend_mod, new_figure_manager, draw_if_interactive, show | ||
|
||
_log.debug('backend %s version %s', name, Backend.backend_version) | ||
return (backend_mod, | ||
Backend.new_figure_manager, | ||
Backend.draw_if_interactive, | ||
Backend.show) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1695,8 +1695,7 @@ def pstoeps(tmpfile, bbox=None, rotated=False): | |
shutil.move(epsfile, tmpfile) | ||
|
||
|
||
class FigureManagerPS(FigureManagerBase): | ||
pass | ||
FigureManagerPS = FigureManagerBase | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a very subtle API change in here if people are looking at There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added an API note. The real intent here is the following: I think the "correct" way to refer to a backend's FigureCanvas, FigureManager, etc. class is under the unsuffixed names (FigureCanvas instead of FigureCanvasPS, etc.) The reason is that 1) these unsuffixed names need to be defined anyways for the backend machinery to pick them up, and 2) some of these classes (FigureManager, Toolbar, though not FigureCanvas of course) can be shared between multiple backends (for example, the xxxcairo backends and mplcairo just reuses the FigureManager classes for each of the GUI toolkits). So sure, I can define (e.g.) FigureManagerQt5 and FigureManagerQt5Agg and FigureManagerQt5Cairo and FigureManagerMplCairoQt to all be aliases (or trivial subclasses) of one another, but that's just muddying the picture IMO; promoting the use of the same FigureManager name everywhere seems clearer. |
||
|
||
|
||
# The following Python dictionary psDefs contains the entries for the | ||
|
@@ -1742,4 +1741,3 @@ class FigureManagerPS(FigureManagerBase): | |
@_Backend.export | ||
class _BackendPS(_Backend): | ||
FigureCanvas = FigureCanvasPS | ||
FigureManager = FigureManagerPS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this block just a big copy/paste? (so we don't have to bother reviewing the code if it is)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is, except for the fact that the FigureManager attribute now defaults to FigureManagerBase.