From 9e253ecd7e3b0542bd3375730e6e39a79a377629 2013-07-17 21:11:58 From: Paul Ivanov Date: 2013-07-17 21:11:58 Subject: [PATCH] Merge pull request #3568 from minrk/pylab-no-import new %matplotlib magic, quieter %pylab magic %matplotlib is a magic which sets up integration but does no imports %pylab now is now as verbose about announcing itself. --- diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 7bc69a7..267c04a 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -55,7 +55,6 @@ from IPython.core.macro import Macro from IPython.core.payload import PayloadManager from IPython.core.prefilter import PrefilterManager from IPython.core.profiledir import ProfileDir -from IPython.core.pylabtools import pylab_activate from IPython.core.prompts import PromptManager from IPython.lib.latextools import LaTeXTool from IPython.testing.skipdoctest import skip_doctest @@ -2838,6 +2837,51 @@ class InteractiveShell(SingletonConfigurable): def enable_gui(self, gui=None): raise NotImplementedError('Implement enable_gui in a subclass') + + def enable_matplotlib(self, gui=None): + """Enable interactive matplotlib and inline figure support. + + This takes the following steps: + + 1. select the appropriate eventloop and matplotlib backend + 2. set up matplotlib for interactive use with that backend + 3. configure formatters for inline figure display + 4. enable the selected gui eventloop + + Parameters + ---------- + gui : optional, string + If given, dictates the choice of matplotlib GUI backend to use + (should be one of IPython's supported backends, 'qt', 'osx', 'tk', + 'gtk', 'wx' or 'inline'), otherwise we use the default chosen by + matplotlib (as dictated by the matplotlib build-time options plus the + user's matplotlibrc configuration file). Note that not all backends + make sense in all contexts, for example a terminal ipython can't + display figures inline. + """ + from IPython.core import pylabtools as pt + gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select) + + if gui != 'inline': + # If we have our first gui selection, store it + if self.pylab_gui_select is None: + self.pylab_gui_select = gui + # Otherwise if they are different + elif gui != self.pylab_gui_select: + print ('Warning: Cannot change to a different GUI toolkit: %s.' + ' Using %s instead.' % (gui, self.pylab_gui_select)) + gui, backend = pt.find_gui_and_backend(self.pylab_gui_select) + + pt.activate_matplotlib(backend) + pt.configure_inline_support(self, backend) + + # Now we must activate the gui pylab wants to use, and fix %run to take + # plot updates into account + self.enable_gui(gui) + self.magics_manager.registry['ExecutionMagics'].default_runner = \ + pt.mpl_runner(self.safe_execfile) + + return gui, backend def enable_pylab(self, gui=None, import_all=True, welcome_message=False): """Activate pylab support at runtime. @@ -2846,6 +2890,8 @@ class InteractiveShell(SingletonConfigurable): namespace all of numpy and pylab, and configures IPython to correctly interact with the GUI event loop. The GUI backend to be used can be optionally selected with the optional ``gui`` argument. + + This method only adds preloading the namespace to InteractiveShell.enable_matplotlib. Parameters ---------- @@ -2857,30 +2903,29 @@ class InteractiveShell(SingletonConfigurable): user's matplotlibrc configuration file). Note that not all backends make sense in all contexts, for example a terminal ipython can't display figures inline. + import_all : optional, bool, default: True + Whether to do `from numpy import *` and `from pylab import *` + in addition to module imports. + welcome_message : deprecated + This argument is ignored, no welcome message will be displayed. """ - from IPython.core.pylabtools import mpl_runner, backends + from IPython.core.pylabtools import import_pylab + + gui, backend = self.enable_matplotlib(gui) + # We want to prevent the loading of pylab to pollute the user's # namespace as shown by the %who* magics, so we execute the activation # code in an empty namespace, and we update *both* user_ns and # user_ns_hidden with this information. ns = {} - try: - gui = pylab_activate(ns, gui, import_all, self, welcome_message=welcome_message) - except KeyError: - error("Backend '%s' not supported. Supported backends are: %s" - % (gui, " ".join(sorted(backends.keys())))) - return - except ImportError: - error("pylab mode doesn't work as matplotlib could not be found." + \ - "\nIs it installed on the system?") - return + import_pylab(ns, import_all) + # warn about clobbered names + ignored = set(["__builtins__"]) + both = set(ns).intersection(self.user_ns).difference(ignored) + clobbered = [ name for name in both if self.user_ns[name] is not ns[name] ] self.user_ns.update(ns) self.user_ns_hidden.update(ns) - # Now we must activate the gui pylab wants to use, and fix %run to take - # plot updates into account - self.enable_gui(gui) - self.magics_manager.registry['ExecutionMagics'].default_runner = \ - mpl_runner(self.safe_execfile) + return gui, backend, clobbered #------------------------------------------------------------------------- # Utilities diff --git a/IPython/core/magics/pylab.py b/IPython/core/magics/pylab.py index e642058..0937752 100644 --- a/IPython/core/magics/pylab.py +++ b/IPython/core/magics/pylab.py @@ -14,30 +14,41 @@ # Our own packages from IPython.config.application import Application +from IPython.core import magic_arguments from IPython.core.magic import Magics, magics_class, line_magic from IPython.testing.skipdoctest import skip_doctest +from IPython.utils.warn import warn +from IPython.core.pylabtools import backends #----------------------------------------------------------------------------- # Magic implementation classes #----------------------------------------------------------------------------- +magic_gui_arg = magic_arguments.argument( + 'gui', nargs='?', + help="""Name of the matplotlib backend to use %s. + If given, the corresponding matplotlib backend is used, + otherwise it will be matplotlib's default + (which you can set in your matplotlib config file). + """ % str(tuple(sorted(backends.keys()))) +) + + @magics_class class PylabMagics(Magics): """Magics related to matplotlib's pylab support""" - + @skip_doctest @line_magic - def pylab(self, parameter_s=''): - """Load numpy and matplotlib to work interactively. - - %pylab [GUINAME] - - This function lets you activate pylab (matplotlib, numpy and - interactive support) at any point during an IPython session. - - It will import at the top level numpy as np, pyplot as plt, matplotlib, - pylab and mlab, as well as all names from numpy and pylab. - + @magic_arguments.magic_arguments() + @magic_gui_arg + def matplotlib(self, line=''): + """Set up matplotlib to work interactively. + + This function lets you activate matplotlib interactive support + at any point during an IPython session. + It does not import anything into the interactive namespace. + If you are using the inline matplotlib backend for embedded figures, you can adjust its behavior via the %config magic:: @@ -49,40 +60,82 @@ class PylabMagics(Magics): # cells: In [2]: %config InlineBackend.close_figures = False - Parameters - ---------- - guiname : optional - One of the valid arguments to the %gui magic ('qt', 'wx', 'gtk', - 'osx' or 'tk'). If given, the corresponding Matplotlib backend is - used, otherwise matplotlib's default (which you can override in your - matplotlib config file) is used. - Examples -------- In this case, where the MPL default is TkAgg:: - In [2]: %pylab - - Welcome to pylab, a matplotlib-based Python environment. - Backend in use: TkAgg - For more information, type 'help(pylab)'. + In [2]: %matplotlib + Using matplotlib backend: TkAgg But you can explicitly request a different backend:: - In [3]: %pylab qt + In [3]: %matplotlib qt + """ + args = magic_arguments.parse_argstring(self.matplotlib, line) + gui, backend = self.shell.enable_matplotlib(args.gui) + self._show_matplotlib_backend(args.gui, backend) - Welcome to pylab, a matplotlib-based Python environment. - Backend in use: Qt4Agg - For more information, type 'help(pylab)'. + @skip_doctest + @line_magic + @magic_arguments.magic_arguments() + @magic_arguments.argument( + '--no-import-all', action='store_true', default=None, + help="""Prevent IPython from performing ``import *`` into the interactive namespace. + + The names that will still be added to the namespace if this flag is given:: + + numpy + matplotlib + np (numpy alias) + plt (matplotlib.pyplot alias) + pylab (from matplotlib) + pyplot (from matplotlib) + mlab (from matplotlib) + display (from IPython) + figsize (from IPython) + getfigs (from IPython) + + You can govern the default behavior with the + InteractiveShellApp.pylab_import_all configurable. """ + ) + @magic_gui_arg + def pylab(self, line=''): + """Load numpy and matplotlib to work interactively. - if Application.initialized(): - app = Application.instance() - try: - import_all_status = app.pylab_import_all - except AttributeError: - import_all_status = True - else: - import_all_status = True + This function lets you activate pylab (matplotlib, numpy and + interactive support) at any point during an IPython session. - self.shell.enable_pylab(parameter_s, import_all=import_all_status, welcome_message=True) + It will import at the top level numpy as np, pyplot as plt, matplotlib, + pylab and mlab, as well as all names from numpy and pylab. + + See the %matplotlib magic for more details. + """ + args = magic_arguments.parse_argstring(self.pylab, line) + if args.no_import_all is None: + # get default from Application + if Application.initialized(): + app = Application.instance() + try: + import_all = app.pylab_import_all + except AttributeError: + import_all = True + else: + # nothing specified, no app - default True + import_all = True + else: + # invert no-import flag + import_all = not args.no_import_all + + gui, backend, clobbered = self.shell.enable_pylab(args.gui, import_all=import_all) + self._show_matplotlib_backend(args.gui, backend) + if clobbered: + warn("pylab import has clobbered these variables: %s" % clobbered + + "\n`%pylab --no-import-all` prevents importing * from pylab and numpy" + ) + + def _show_matplotlib_backend(self, gui, backend): + """show matplotlib message backend message""" + if not gui or gui == 'auto': + print ("using matplotlib backend: %s" % backend) + diff --git a/IPython/core/pylabtools.py b/IPython/core/pylabtools.py index a41ff58..806139c 100644 --- a/IPython/core/pylabtools.py +++ b/IPython/core/pylabtools.py @@ -231,7 +231,7 @@ def activate_matplotlib(backend): import matplotlib matplotlib.interactive(True) - + # Matplotlib had a bug where even switch_backend could not force # the rcParam to update. This needs to be set *before* the module # magic of switch_backend(). @@ -251,7 +251,13 @@ def activate_matplotlib(backend): def import_pylab(user_ns, import_all=True): - """Import the standard pylab symbols into user_ns.""" + """Populate the namespace with pylab-related values. + + Imports matplotlib, pylab, numpy, and everything from pylab and numpy. + + Also imports a few names from IPython (figsize, display, getfigs) + + """ # Import numpy as np/pyplot as plt are conventions we're trying to # somewhat standardize on. Making them available to users by default @@ -263,14 +269,21 @@ def import_pylab(user_ns, import_all=True): "plt = pyplot\n" ) exec s in user_ns - + if import_all: s = ("from matplotlib.pylab import *\n" "from numpy import *\n") exec s in user_ns + + # IPython symbols to add + user_ns['figsize'] = figsize + from IPython.core.display import display + # Add display and getfigs to the user's namespace + user_ns['display'] = display + user_ns['getfigs'] = getfigs -def configure_inline_support(shell, backend, user_ns=None): +def configure_inline_support(shell, backend): """Configure an IPython shell object for matplotlib use. Parameters @@ -278,10 +291,6 @@ def configure_inline_support(shell, backend, user_ns=None): shell : InteractiveShell instance backend : matplotlib backend - - user_ns : dict - A namespace where all configured variables will be placed. If not given, - the `user_ns` attribute of the shell object is used. """ # If using our svg payload backend, register the post-execution # function that will pick up the results for display. This can only be @@ -296,8 +305,6 @@ def configure_inline_support(shell, backend, user_ns=None): return from matplotlib import pyplot - user_ns = shell.user_ns if user_ns is None else user_ns - cfg = InlineBackend.instance(parent=shell) cfg.shell = shell if cfg not in shell.configurables: @@ -313,8 +320,6 @@ def configure_inline_support(shell, backend, user_ns=None): shell._saved_rcParams[k] = pyplot.rcParams[k] # load inline_rc pyplot.rcParams.update(cfg.rc) - # Add 'figsize' to pyplot and to the user's namespace - user_ns['figsize'] = pyplot.figsize = figsize else: from IPython.kernel.zmq.pylab.backend_inline import flush_figures if flush_figures in shell._post_execute: @@ -324,62 +329,5 @@ def configure_inline_support(shell, backend, user_ns=None): del shell._saved_rcParams # Setup the default figure format - fmt = cfg.figure_format - select_figure_format(shell, fmt) - - # The old pastefig function has been replaced by display - from IPython.core.display import display - # Add display and getfigs to the user's namespace - user_ns['display'] = display - user_ns['getfigs'] = getfigs - - -def pylab_activate(user_ns, gui=None, import_all=True, shell=None, welcome_message=False): - """Activate pylab mode in the user's namespace. - - Loads and initializes numpy, matplotlib and friends for interactive use. - - Parameters - ---------- - user_ns : dict - Namespace where the imports will occur. - - gui : optional, string - A valid gui name following the conventions of the %gui magic. - - import_all : optional, boolean - If true, an 'import *' is done from numpy and pylab. + select_figure_format(shell, cfg.figure_format) - welcome_message : optional, boolean - If true, print a welcome message about pylab, which includes the backend - being used. - - Returns - ------- - The actual gui used (if not given as input, it was obtained from matplotlib - itself, and will be needed next to configure IPython's gui integration. - """ - pylab_gui_select = shell.pylab_gui_select if shell is not None else None - # Try to find the appropriate gui and backend for the settings - gui, backend = find_gui_and_backend(gui, pylab_gui_select) - if shell is not None and gui != 'inline': - # If we have our first gui selection, store it - if pylab_gui_select is None: - shell.pylab_gui_select = gui - # Otherwise if they are different - elif gui != pylab_gui_select: - print ('Warning: Cannot change to a different GUI toolkit: %s.' - ' Using %s instead.' % (gui, pylab_gui_select)) - gui, backend = find_gui_and_backend(pylab_gui_select) - activate_matplotlib(backend) - import_pylab(user_ns, import_all) - if shell is not None: - configure_inline_support(shell, backend, user_ns) - if welcome_message: - print """ -Welcome to pylab, a matplotlib-based Python environment [backend: %s]. -For more information, type 'help(pylab)'.""" % backend - # flush stdout, just to be safe - sys.stdout.flush() - - return gui diff --git a/IPython/core/shellapp.py b/IPython/core/shellapp.py index d9ca71d..e98ec39 100644 --- a/IPython/core/shellapp.py +++ b/IPython/core/shellapp.py @@ -176,8 +176,11 @@ class InteractiveShellApp(Configurable): """ ) pylab_import_all = Bool(True, config=True, - help="""If true, an 'import *' is done from numpy and pylab, - when using pylab""" + help="""If true, IPython will populate the user namespace with numpy, pylab, etc. + and an 'import *' is done from numpy and pylab, when using pylab mode. + + When False, pylab mode should not import any names into the user namespace. + """ ) shell = Instance('IPython.core.interactiveshell.InteractiveShellABC') @@ -198,7 +201,9 @@ class InteractiveShellApp(Configurable): gui, backend = pylabtools.find_gui_and_backend(self.pylab) self.log.info("Enabling GUI event loop integration, " "toolkit=%s, pylab=%s" % (gui, self.pylab)) - shell.enable_pylab(gui, import_all=self.pylab_import_all, welcome_message=True) + if self.pylab == "auto": + print ("using matplotlib backend: %s" % backend) + shell.enable_pylab(self.pylab, import_all=self.pylab_import_all) else: self.log.info("Enabling GUI event loop integration, " "toolkit=%s" % self.gui) diff --git a/IPython/core/tests/test_pylabtools.py b/IPython/core/tests/test_pylabtools.py index 5ca481a..4ee672a 100644 --- a/IPython/core/tests/test_pylabtools.py +++ b/IPython/core/tests/test_pylabtools.py @@ -23,6 +23,7 @@ from matplotlib import pyplot as plt import numpy as np # Our own imports +from IPython.core.interactiveshell import InteractiveShell from IPython.testing import decorators as dec from .. import pylabtools as pt @@ -62,11 +63,11 @@ def test_import_pylab(): nt.assert_true('plt' in ns) nt.assert_equal(ns['np'], np) - class TestPylabSwitch(object): - class Shell(object): - pylab_gui_select = None - + class Shell(InteractiveShell): + def enable_gui(self, gui): + pass + def setup(self): import matplotlib def act_mpl(backend): @@ -93,47 +94,47 @@ class TestPylabSwitch(object): def test_qt(self): s = self.Shell() - gui = pt.pylab_activate(dict(), None, False, s) + gui, backend = s.enable_matplotlib(None) nt.assert_equal(gui, 'qt') nt.assert_equal(s.pylab_gui_select, 'qt') - gui = pt.pylab_activate(dict(), 'inline', False, s) + gui, backend = s.enable_matplotlib('inline') nt.assert_equal(gui, 'inline') nt.assert_equal(s.pylab_gui_select, 'qt') - gui = pt.pylab_activate(dict(), 'qt', False, s) + gui, backend = s.enable_matplotlib('qt') nt.assert_equal(gui, 'qt') nt.assert_equal(s.pylab_gui_select, 'qt') - gui = pt.pylab_activate(dict(), 'inline', False, s) + gui, backend = s.enable_matplotlib('inline') nt.assert_equal(gui, 'inline') nt.assert_equal(s.pylab_gui_select, 'qt') - gui = pt.pylab_activate(dict(), None, False, s) + gui, backend = s.enable_matplotlib() nt.assert_equal(gui, 'qt') nt.assert_equal(s.pylab_gui_select, 'qt') def test_inline(self): s = self.Shell() - gui = pt.pylab_activate(dict(), 'inline', False, s) + gui, backend = s.enable_matplotlib('inline') nt.assert_equal(gui, 'inline') nt.assert_equal(s.pylab_gui_select, None) - gui = pt.pylab_activate(dict(), 'inline', False, s) + gui, backend = s.enable_matplotlib('inline') nt.assert_equal(gui, 'inline') nt.assert_equal(s.pylab_gui_select, None) - gui = pt.pylab_activate(dict(), 'qt', False, s) + gui, backend = s.enable_matplotlib('qt') nt.assert_equal(gui, 'qt') nt.assert_equal(s.pylab_gui_select, 'qt') def test_qt_gtk(self): s = self.Shell() - gui = pt.pylab_activate(dict(), 'qt', False, s) + gui, backend = s.enable_matplotlib('qt') nt.assert_equal(gui, 'qt') nt.assert_equal(s.pylab_gui_select, 'qt') - gui = pt.pylab_activate(dict(), 'gtk', False, s) + gui, backend = s.enable_matplotlib('gtk') nt.assert_equal(gui, 'qt') nt.assert_equal(s.pylab_gui_select, 'qt') diff --git a/IPython/kernel/inprocess/ipkernel.py b/IPython/kernel/inprocess/ipkernel.py index 73d5955..0b2bbae 100644 --- a/IPython/kernel/inprocess/ipkernel.py +++ b/IPython/kernel/inprocess/ipkernel.py @@ -160,19 +160,23 @@ class InProcessInteractiveShell(ZMQInteractiveShell): #------------------------------------------------------------------------- def enable_gui(self, gui=None): - """ Enable GUI integration for the kernel. - """ + """Enable GUI integration for the kernel.""" from IPython.kernel.zmq.eventloops import enable_gui if not gui: gui = self.kernel.gui - enable_gui(gui, kernel=self.kernel) + return enable_gui(gui, kernel=self.kernel) + + def enable_matplotlib(self, gui=None): + """Enable matplotlib integration for the kernel.""" + if not gui: + gui = self.kernel.gui + return super(InProcessInteractiveShell, self).enable_matplotlib(gui) def enable_pylab(self, gui=None, import_all=True, welcome_message=False): - """ Activate pylab support at runtime. - """ + """Activate pylab support at runtime.""" if not gui: gui = self.kernel.gui - super(InProcessInteractiveShell, self).enable_pylab(gui, import_all, + return super(InProcessInteractiveShell, self).enable_pylab(gui, import_all, welcome_message) InteractiveShellABC.register(InProcessInteractiveShell) diff --git a/IPython/kernel/inprocess/tests/test_kernel.py b/IPython/kernel/inprocess/tests/test_kernel.py index a5ec8c1..091886a 100644 --- a/IPython/kernel/inprocess/tests/test_kernel.py +++ b/IPython/kernel/inprocess/tests/test_kernel.py @@ -42,7 +42,7 @@ class InProcessKernelTestCase(unittest.TestCase): kc = self.kc kc.execute('%pylab') msg = get_stream_message(kc) - self.assert_('Welcome to pylab' in msg['content']['data']) + self.assert_('matplotlib' in msg['content']['data']) def test_raw_input(self): """ Does the in-process kernel handle raw_input correctly? diff --git a/IPython/lib/tests/test_irunner_pylab_magic.py b/IPython/lib/tests/test_irunner_pylab_magic.py index 2b0fc4b..a474993 100644 --- a/IPython/lib/tests/test_irunner_pylab_magic.py +++ b/IPython/lib/tests/test_irunner_pylab_magic.py @@ -83,8 +83,7 @@ In \[1\]: from IPython\.config\.application import Application In \[2\]: app = Application\.instance\(\) In \[3\]: app\.pylab_import_all = True In \[4\]: pylab -^Welcome to pylab, a matplotlib-based Python environment -For more information, type 'help\(pylab\)'\. +^using matplotlib backend: In \[5\]: ip=get_ipython\(\) In \[6\]: \'plot\' in ip\.user_ns Out\[6\]: True @@ -109,8 +108,7 @@ In \[1\]: from IPython\.config\.application import Application In \[2\]: app = Application\.instance\(\) In \[3\]: app\.pylab_import_all = False In \[4\]: pylab -^Welcome to pylab, a matplotlib-based Python environment -For more information, type 'help\(pylab\)'\. +^using matplotlib backend: In \[5\]: ip=get_ipython\(\) In \[6\]: \'plot\' in ip\.user_ns Out\[6\]: False diff --git a/IPython/utils/decorators.py b/IPython/utils/decorators.py index 208bf83..c264855 100644 --- a/IPython/utils/decorators.py +++ b/IPython/utils/decorators.py @@ -33,6 +33,10 @@ def flag_calls(func): Testing for truth in wrapper.called allows you to determine if a call to func() was attempted and succeeded.""" + + # don't wrap twice + if hasattr(func, 'called'): + return func def wrapper(*args,**kw): wrapper.called = False