Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions ipykernel/kernelapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,15 @@ def init_extensions(self):
self.log.debug('ipywidgets package not installed. Widgets will not be available.')
# END HARDCODED WIDGETS HACK

mpl_plots_inline = Bool(True, config=True,
help="Make matplotlib send plots to Jupyter frontends by default."
)

def init_mpl_shim(self):
if self.mpl_plots_inline:
from . import mplshim
mplshim.install_import_hook()

@catch_config_error
def initialize(self, argv=None):
super(IPKernelApp, self).initialize(argv)
Expand All @@ -387,6 +396,7 @@ def initialize(self, argv=None):
self.init_gui_pylab()
self.init_extensions()
self.init_code()
self.init_mpl_shim()
# flush stdout/stderr, so that anything written to these streams during
# initialization do not get associated with the first execution request
sys.stdout.flush()
Expand Down
89 changes: 89 additions & 0 deletions ipykernel/mplshim.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"""Import hooks to activate our inline backend when matplotlib is loaded
"""

import imp
import sys

def set_inline_backend(mpl):
from IPython.core.pylabtools import backends, configure_inline_support
from .kernelapp import IPKernelApp
mpl.interactive(True)
mpl.use(backends['inline'])
configure_inline_support(IPKernelApp.instance().shell, backends['inline'])

class Py2MPLFinderShim(object):
@classmethod
def find_module(cls, fullname, path=None):
if (fullname != 'matplotlib') or (path is not None):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting the hook on matplotlib makes it impossible for

import matplotlib
matplotlib.use('something')

to work. Is there something else we can look for that wouldn't preempt explicit backend selection? I've used pyplot in my own version of this, but I'm not sure if it's sufficiently general.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the only problem is that the import hook loads our backend (and pyplot) in order to set up the config changes and post-execute hooks that the inline backends need. If we can delay that stuff (the call to configure_inline_support()) until the backend is actually loaded, I think it would be fine to set the backend on loading matplotlib, because the setting isn't committed until matplotlib.backends is loaded.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was also discussion at scipy with @WeatherGod to delay actually forcing the back end until the first figure is created.

return None

try:
res = imp.find_module('matplotlib')
except ImportError:
return None
else:
return Py2MPLLoaderShim(*res)

class Py2MPLLoaderShim(object):
def __init__(self, file, pathname, description):
self.file = file
self.pathname = pathname
self.description = description

def load_module(self, fullname):
# fullname should only ever be 'matplotlib' here
m = imp.load_module(fullname, self.file, self.pathname, self.description)
set_inline_backend(m)
return m

try:
from importlib.machinery import PathFinder
except ImportError:
# Python 2
finder_shim = Py2MPLFinderShim
else:
class Py3MPLFinderShim(PathFinder):
# On Python 3.4 and above, find_spec() will be called
@classmethod
def find_spec(cls, fullname, path=None, target=None):
if (fullname != 'matplotlib') or (path is not None):
return None

spec = PathFinder.find_spec(fullname, path=None)
if spec is None:
return None

if spec.loader is not None:
spec.loader = Py3MPLLoaderShim(spec.loader)
return spec

# On Python 3.3 or below, find_module() will be called
@classmethod
def find_module(cls, fullname, path=None):
print(fullname, path)
if (fullname != 'matplotlib') or (path is not None):
return None

real_loader = PathFinder.find_module(fullname, path=None)
if real_loader is None:
return None
return Py3MPLLoaderShim(real_loader)

class Py3MPLLoaderShim(object):
def __init__(self, real_loader):
self.real_loader = real_loader

def load_module(self, fullname):
# fullname should only ever be 'matplotlib' here)
m = self.real_loader.load_module(fullname)
set_inline_backend(m)
return m


finder_shim = Py3MPLFinderShim

def install_import_hook():
sys.meta_path.insert(0, finder_shim)

def remove_import_hook():
sys.meta_path.remove(finder_shim)