Repository: ipython/ipython

Issue:
pylab=inline fig.show() non-existent in notebook
I have some code that draws some plots and I notice it raises an AttributeError on the show() method of a figure object. I don't really know the details but I assume that IPython is somehow providing its own backend. Inspecting a figure object created with backend 'tk' or 'wx' I notice that `show()` doesn't seem to be a bound method but a function, pointing at the respective backend's implementation of `show()`?

It seems like figure objects should provide this in the context of notebook inline plotting, even if it's a no-op, for the sake of not breaking existing code.



Discussion:
Could you provide us with a full traceback as well as at least the snippet that triggers the problem (if it's not easy to produce a small, self-contained example)?  It will make the debugging far easier...

Hey Fernando,

It's just an `AttributeError: 'Figure' object has no attribute 'show'` with the traceback entirely in my own code.

Here's a snippet that runs fine in `ipython --pylab` but raises the AttributeError in `ipython notebook --pylab=inline`:

``` python
import numpy as np
import matplotlib.pyplot as plt

plt.ioff()                                                                      
x = np.random.uniform(-5, 5, size=(100))
y = np.random.uniform(-5, 5, size=(100))
f = plt.figure()
plt.scatter(x, y)
plt.plot(y)
f.show()
```

Mmh, I see.  It turns out that `.show()` is a method that gets added dynamically after the fact by the backends, which monkeypatch the figure object:

```
In [1]: f  =figure()

In [2]: f.show??
Type:       function
Base Class: <type 'function'>
String Form:<function <lambda> at 0xafe0cdc>
Namespace:  Interactive
File:       /home/fperez/usr/opt/lib/python2.7/site-packages/matplotlib/backends/backend_qt4.py
Definition: f.show(*args)
Source:             self.canvas.figure.show = lambda *args: self.window.show()
```

That's kind of nasty, because it means all backends have to manually go around patching up the figure objects they create.  I'm pinging here @jdh2358 and @mdboom to get their take on this one...  Guys, did I misunderstand things?  I thought all a backend needed to do was to implement the handful of things our inline backend does... I'm happy to fix our code if that's really where the problem is (and probably will regardless, to alleviate @dwf's problems), but I'd like to also sort out the question at the MPL level.

Much as I've suspected. I've patched my own code to just catch the AttributeError and ignore it, so it doesn't matter much to me, but it might surprise other users who don't realize what's going on.

Granted, you're absolutely right re: MPL, it's a bit weird that backends are expected to do this monkey-patching, especially if it's not documented for new backend-writers. It seems like you could probably accomplish this with some backend-specific dispatch mechanism in the context of a more traditional `show()`, but there might be reasons for the current way of doing things.

This caught me by surprise too -- this looks like an undocumented feature.  Eg, in backend_tkagg.py, we write (https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backends/backend_tkagg.py#L482):

```
    # attach a show method to the figure for pylab ease of use
    self.canvas.figure.show = lambda *args: self.show()
```

According to git blame, it's my fault....  I added it in 2007 - https://github.com/matplotlib/matplotlib/commit/884a5665367e24c52ce3d2635304926af02667ff.  Git has a better memory than I do :-)

Easiest thing is for you to add it to your backend, and we will document it in backend_bases.py and backend_template.py -- I created this issue https://github.com/matplotlib/matplotlib/issues/835

John, wouldn't it be cleaner to have a show method in the base Figure object, that simply does

```
def show(self):
   plt.show(self)
```

?  That way it would cover every backend out of the box, without the need to go manually fixing each one (for example, we don't know if the native OSX one is doing this correctly or not, I can't test that one).

Figure instance doesn't know about pyplot.  And I'm reasonably sure macosx doesn't implement this because it did not exist when I made the initial patch, and since it wasn't properly documented Michiel wouldn't have known to add it most likely

Understood, but my point stands (perhaps with a different spelling): isn't it possible to fix this at the base figure/canvas level and have it simply delegate to what the backends already do?  It seems that the necessary fix is effectively to copy boilerplate equivalent to:

```
self.canvas.figure.show = lambda *args: self.show()
```

into every backend.  If that's really the case, then the right solution is to do it _once_ in the right place, instead of having N copies of this more or less identical code in every single backend (with bugs in those who fail/forget to do so).

It could be possible, but these are expressed in the current implementation at the level of the FigureManager.  The Figure knows about the FigureCanvas, but not about the FigureManager.   FigureManager is an abstraction designed for pyplot, which manages raising the figure etc.  People who are embedding mpl in the GUI are probably not using the manager at all.  Basically, it is a convenience method hack for pyplot users who want to use a figure method.  We could do something like:

```
class Figure:
    def show(self):
        manager = getattr(self.canvas, 'manager')
        if manager is not None:
            manager.show()

# and then define a do-nothing default "def show" for the manager (so backend implementers are aware to target it)

class FigureManagerBase:
    def show(self):
        'raise the figure window and redraw'
        pass
```

but we don't have anything like this concept currently (it's a series of monkey patches) and testing and implementing across backends and toolkits is a bit of work. Stay tuned.

Got it; we'll fix it on our side in the backend, and mpl can consider a longer-term design for this.


Resolving patch:
diff --git a/IPython/zmq/pylab/backend_inline.py b/IPython/zmq/pylab/backend_inline.py
--- a/IPython/zmq/pylab/backend_inline.py
+++ b/IPython/zmq/pylab/backend_inline.py
@@ -117,13 +117,32 @@ def draw_if_interactive():
     """
     Is called after every pylab drawing command
     """
-    # signal that the current active figure should be sent at the end of execution.
-    # Also sets the _draw_called flag, signaling that there will be something to send.
-    # At the end of the code execution, a separate call to flush_figures()
-    # will act upon these values
-    
+    # signal that the current active figure should be sent at the end of
+    # execution.  Also sets the _draw_called flag, signaling that there will be
+    # something to send.  At the end of the code execution, a separate call to
+    # flush_figures() will act upon these values
+
     fig = Gcf.get_active().canvas.figure
+
+    # Hack: matplotlib FigureManager objects in interacive backends (at least
+    # in some of them) monkeypatch the figure object and add a .show() method
+    # to it.  This applies the same monkeypatch in order to support user code
+    # that might expect `.show()` to be part of the official API of figure
+    # objects.
+    # For further reference:
+    # https://github.com/ipython/ipython/issues/1612
+    # https://github.com/matplotlib/matplotlib/issues/835
     
+    if not hasattr(fig, 'show'):
+        # Queue up `fig` for display
+        fig.show = lambda *a: send_figure(fig)
+
+    # If matplotlib was manually set to non-interactive mode, this function
+    # should be a no-op (otherwise we'll generate duplicate plots, since a user
+    # who set ioff() manually expects to make separate draw/show calls).
+    if not matplotlib.is_interactive():
+        return
+
     # ensure current figure will be drawn, and each subsequent call
     # of draw_if_interactive() moves the active figure to ensure it is
     # drawn last
@@ -132,9 +151,11 @@ def draw_if_interactive():
     except ValueError:
         # ensure it only appears in the draw list once
         pass
+    # Queue up the figure for drawing in next show() call
     show._to_draw.append(fig)
     show._draw_called = True
 
+
 def flush_figures():
     """Send all figures that changed
 
@@ -184,7 +205,7 @@ def flush_figures():
 
 
 def send_figure(fig):
-    """Draw the current figure and send it as a PNG payload.
+    """Draw the given figure and send it as a PNG payload.
     """
     fmt = InlineBackend.instance().figure_format
     data = print_figure(fig, fmt)


Test patch:
diff --git a/docs/examples/tests/pylab_figshow.py b/docs/examples/tests/pylab_figshow.py
new file mode 100644
--- /dev/null
+++ b/docs/examples/tests/pylab_figshow.py
@@ -0,0 +1,23 @@
+"""Manual test for figure.show() in the inline matplotlib backend.
+
+This script should be loaded for interactive use (via %load) into a qtconsole
+or notebook initialized with the pylab inline backend.  
+
+Expected behavior: only *one* copy of the figure is shown.
+
+
+For further details:
+https://github.com/ipython/ipython/issues/1612
+https://github.com/matplotlib/matplotlib/issues/835
+"""
+
+import numpy as np
+import matplotlib.pyplot as plt
+
+plt.ioff()                                                                      
+x = np.random.uniform(-5, 5, size=(100))
+y = np.random.uniform(-5, 5, size=(100))
+f = plt.figure()
+plt.scatter(x, y)
+plt.plot(y)
+f.show()