jupyterlite / jupyterlite

Wasm powered Jupyter running in the browser 💡
https://jupyterlite.rtfd.io/en/stable/try/lab
BSD 3-Clause "New" or "Revised" License
3.9k stars 306 forks source link

Execution of "%matplotlib notebook" before "import matplotlib" fails #965

Open boisgera opened 1 year ago

boisgera commented 1 year ago

Description

%matplotlib notebook

generates the error

---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 1
----> 1 get_ipython().run_line_magic('matplotlib', 'notebook')

File /lib/python3.10/site-packages/IPython/core/interactiveshell.py:2369, in InteractiveShell.run_line_magic(self, magic_name, line, _stack_depth)
   2367     kwargs['local_ns'] = self.get_local_scope(stack_depth)
   2368 with self.builtin_trap:
-> 2369     result = fn(*args, **kwargs)
   2370 return result

File /lib/python3.10/site-packages/IPython/core/magics/pylab.py:99, in PylabMagics.matplotlib(self, line)
     97     print("Available matplotlib backends: %s" % backends_list)
     98 else:
---> 99     gui, backend = self.shell.enable_matplotlib(args.gui.lower() if isinstance(args.gui, str) else args.gui)
    100     self._show_matplotlib_backend(args.gui, backend)

File /lib/python3.10/site-packages/IPython/core/interactiveshell.py:3522, in InteractiveShell.enable_matplotlib(self, gui)
   3501 def enable_matplotlib(self, gui=None):
   3502     """Enable interactive matplotlib and inline figure support.
   3503 
   3504     This takes the following steps:
   (...)
   3520         display figures inline.
   3521     """
-> 3522     from matplotlib_inline.backend_inline import configure_inline_support
   3524     from IPython.core import pylabtools as pt
   3525     gui, backend = pt.find_gui_and_backend(gui, self.pylab_gui_select)

File /lib/python3.10/site-packages/matplotlib_inline/__init__.py:1
----> 1 from . import backend_inline, config  # noqa
      2 __version__ = "0.1.6"  # noqa

File /lib/python3.10/site-packages/matplotlib_inline/backend_inline.py:6
      1 """A matplotlib backend for publishing figures via display_data"""
      3 # Copyright (c) IPython Development Team.
      4 # Distributed under the terms of the BSD 3-Clause License.
----> 6 import matplotlib
      7 from matplotlib import colors
      8 from matplotlib.backends import backend_agg

ModuleNotFoundError: The module 'matplotlib' is included in the Pyodide distribution, but it is not installed.
You can install it by calling:
  await micropip.install("matplotlib") in Python, or
  await pyodide.loadPackage("matplotlib") in JavaScript
See https://pyodide.org/en/stable/usage/loading-packages.html for more details.

Reproduce

  1. On a new jupyter lite notebook
  2. Type %import matplotlib in the first cell. Do NOT import matplotib beforehand
  3. Execute the cell.

Expected behavior

This code should execute without any visible output (and enable the notebook mode for matplotlib). At the moment, this works if you import matplotlib beforehand but in the Jupyter lab context both options work.

Context

Jupyter lite version : Version 0.1.0-beta.17

qqdaiyu55 commented 1 year ago

matplotlib is not installed by default in jupyterlite, you need to execute %pip install matplotlib before execute your codes.

boisgera commented 1 year ago

AFAICT, it is actually installed by default. At least I can import it and make some plots without any pip install:

Screenshot from 2023-01-27 09-49-14

My issue here is that in Jupyterlite,

%matplotlib notebook
import matplotlib

won't work

while

import matplotlib
%matplotlib notebook

does. Both work in the Jupyter lab setting (AFAICT).

qqdaiyu55 commented 1 year ago

@boisgera For jupyterlite, it use pyodide.runPython(code_string) to run codes. when code_string == "import matplotlib", pyodide can detect it (like import pkg_name regex), and will help user to automatically and silently install matplotlib (You can open developer tool > Network tab, to see the network requests to confirm it). when code_string == "%matplotlib notebook", import matplotlib is invoked inside but cannot be detected by pyodide.

boisgera commented 1 year ago

Ah, I was not aware of that, thanks @qqdaiyu55 , it's enlightening! And indeed, if I do for example

exec("import matplotlib.pyplot as plt")

in a notebook, I end up with the same failure and error message. Same thing if I define a matplotlib_loader.py file whose content is:

import matplotlib

and make a

from matplotlib_loader import *

in the notebook.


Would it be possible to use import hooks (https://docs.python.org/3/reference/import.html#import-hooks) to have the same auto-loading property on import, but with a more robust behavior ? (Any import could be detected this way AFAICT, including the one triggered by %matplotlib notebook.)

Regards,

Sébastien