Repository: ipython/ipython

Issue:
Same widget does not update all interact. 
Cf [Stackoverflow](https://stackoverflow.com/questions/25945543/can-values-from-ipython-widgets-propagate-to-other-cells/25992790#25992790) 

having the same widget (say a Checkbox) shared by 2 interact, the value of the checkbox is updated across cell, but the interactived' function not call. Ex:

![capture d ecran 2014-09-24 a 08 30 40](https://cloud.githubusercontent.com/assets/335567/4384325/e7c2de98-43b4-11e4-9264-74546270de13.png)

I clicked on first checkbox, second was highligted, but value did not changed from True to False.



Discussion:
Thanks for opening this. 

@jdfreder can you have a look at this?

If two cells have exactly the same overall widget hierarchy displayed, then I think this behavior makes sense. But it is entirely possible that a single widget will appear in multiple cells that have completely different overall widget hierarchies. I think the old behavior might make sense. IIRC this was a deliberate choice that we made early on, but @jdfreder will (hopefully) remember more. 

We also need to check to see how this interplays with the output widget.

ok, that sounds much too complicated to be appropriate for 3.0. I think we should table this, and mark as 4.0.

OK sounds good.

On Thu, Jan 15, 2015 at 2:45 PM, Min RK notifications@github.com wrote:

> ok, that sounds much too complicated to be appropriate for 3.0. I think we
> should table this, and mark as 4.0.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/ipython/ipython/issues/6534#issuecomment-70177052.

## 

Brian E. Granger
Cal Poly State University, San Luis Obispo
@ellisonbg on Twitter and GitHub
bgranger@calpoly.edu and ellisonbg@gmail.com

If interact used an output widget context manager to wrap the output and included that output in the Box widget that all of the other interact controls are in, I don't think you'd see this problem.  I think what's happening here is that the output is only getting routed to the active cell (as designed).

+1 on making this for 4.0.

+1 to having stdout/stderr by default only going to the cell the action happened on, but explicitly capturing output in an output widget would display the output everywhere there was the output widget view.  In other words, +1 to @jdfreder's comment, if I understood him correctly.


Resolving patch:
diff --git a/IPython/html/widgets/interaction.py b/IPython/html/widgets/interaction.py
--- a/IPython/html/widgets/interaction.py
+++ b/IPython/html/widgets/interaction.py
@@ -14,7 +14,7 @@
 from IPython.core.getipython import get_ipython
 from IPython.html.widgets import (Widget, Text,
     FloatSlider, IntSlider, Checkbox, Dropdown,
-    Box, Button, DOMWidget)
+    Box, Button, DOMWidget, Output)
 from IPython.display import display, clear_output
 from IPython.utils.py3compat import string_types, unicode_type
 from IPython.utils.traitlets import HasTraits, Any, Unicode
@@ -204,29 +204,34 @@ def interactive(__interact_f, **kwargs):
     if manual:
         manual_button = Button(description="Run %s" % f.__name__)
         c.append(manual_button)
+
+    # Use an output widget to capture the output of interact.
+    output = Output()
+    c.append(output)
     container.children = c
 
     # Build the callback
     def call_f(name=None, old=None, new=None):
-        container.kwargs = {}
-        for widget in kwargs_widgets:
-            value = widget.value
-            container.kwargs[widget._kwarg] = value
-        if co:
-            clear_output(wait=True)
-        if manual:
-            manual_button.disabled = True
-        try:
-            container.result = f(**container.kwargs)
-        except Exception as e:
-            ip = get_ipython()
-            if ip is None:
-                container.log.warn("Exception in interact callback: %s", e, exc_info=True)
-            else:
-                ip.showtraceback()
-        finally:
+        with output:
+            container.kwargs = {}
+            for widget in kwargs_widgets:
+                value = widget.value
+                container.kwargs[widget._kwarg] = value
+            if co:
+                clear_output(wait=True)
             if manual:
-                manual_button.disabled = False
+                manual_button.disabled = True
+            try:
+                container.result = f(**container.kwargs)
+            except Exception as e:
+                ip = get_ipython()
+                if ip is None:
+                    container.log.warn("Exception in interact callback: %s", e, exc_info=True)
+                else:
+                    ip.showtraceback()
+            finally:
+                if manual:
+                    manual_button.disabled = False
 
     # Wire up the widgets
     # If we are doing manual running, the callback is only triggered by the button
diff --git a/IPython/html/widgets/widget_output.py b/IPython/html/widgets/widget_output.py
--- a/IPython/html/widgets/widget_output.py
+++ b/IPython/html/widgets/widget_output.py
@@ -34,43 +34,53 @@ class Output(DOMWidget):
             print('prints to output widget')"""
     _view_name = Unicode('OutputView', sync=True)
 
+    def __init__(self, *args, **kwargs):
+        super(Output, self).__init__(*args, **kwargs)
+        from IPython import get_ipython
+        ip = get_ipython()
+        if ip is not None and hasattr(ip, 'kernel'):
+            self._kernel = ip.kernel
+        else:
+            self._kernel = None
+
     def clear_output(self, *pargs, **kwargs):
         with self:
             clear_output(*pargs, **kwargs)
 
     def __enter__(self):
         """Called upon entering output widget context manager."""
-        self._flush()
-        kernel = get_ipython().kernel
-        session = kernel.session
-        send = session.send
-        self._original_send = send
-        self._session = session
-
-        def send_hook(stream, msg_or_type, content=None, parent=None, ident=None,
-             buffers=None, track=False, header=None, metadata=None): 
-
-            # Handle both prebuild messages and unbuilt messages.
-            if isinstance(msg_or_type, (Message, dict)):
-                msg_type = msg_or_type['msg_type']
-                msg = dict(msg_or_type)
-            else:
-                msg_type = msg_or_type
-                msg = session.msg(msg_type, content=content, parent=parent, 
-                    header=header, metadata=metadata)
-
-            # If this is a message type that we want to forward, forward it.
-            if stream is kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']:
-                self.send(msg)
-            else: 
-                send(stream, msg, ident=ident, buffers=buffers, track=track)
-
-        session.send = send_hook
+        if self._kernel is not None:
+            self._flush()
+            session = self._kernel.session
+            send = session.send
+            self._original_send = send
+            self._session = session
+
+            def send_hook(stream, msg_or_type, content=None, parent=None, ident=None,
+                 buffers=None, track=False, header=None, metadata=None): 
+
+                # Handle both prebuild messages and unbuilt messages.
+                if isinstance(msg_or_type, (Message, dict)):
+                    msg_type = msg_or_type['msg_type']
+                    msg = dict(msg_or_type)
+                else:
+                    msg_type = msg_or_type
+                    msg = session.msg(msg_type, content=content, parent=parent, 
+                        header=header, metadata=metadata)
+
+                # If this is a message type that we want to forward, forward it.
+                if stream is self._kernel.iopub_socket and msg_type in ['clear_output', 'stream', 'display_data']:
+                    self.send(msg)
+                else: 
+                    send(stream, msg, ident=ident, buffers=buffers, track=track)
+
+            session.send = send_hook
 
     def __exit__(self, exception_type, exception_value, traceback):
         """Called upon exiting output widget context manager."""
-        self._flush()
-        self._session.send = self._original_send
+        if self._kernel is not None:
+            self._flush()
+            self._session.send = self._original_send
 
     def _flush(self):
         """Flush stdout and stderr buffers."""


Test patch:
diff --git a/IPython/html/widgets/tests/test_interaction.py b/IPython/html/widgets/tests/test_interaction.py
--- a/IPython/html/widgets/tests/test_interaction.py
+++ b/IPython/html/widgets/tests/test_interaction.py
@@ -80,7 +80,8 @@ def check_widgets(container, **to_check):
     # build a widget dictionary, so it matches
     widgets = {}
     for w in container.children:
-        widgets[w.description] = w
+        if hasattr(w, 'description'):
+            widgets[w.description] = w
     
     for key, d in to_check.items():
         nt.assert_in(key, widgets)
@@ -138,7 +139,7 @@ def test_single_value_float():
 def test_single_value_int():
     for a in (1, 5, -3):
         c = interactive(f, a=a)
-        nt.assert_equal(len(c.children), 1)
+        nt.assert_equal(len(c.children), 2)
         w = c.children[0]
         check_widget(w,
             cls=widgets.IntSlider,
@@ -157,7 +158,7 @@ def test_list_tuple_2_int():
         c = interactive(f, tup=(1,-1))
     for min, max in [ (0,1), (1,10), (1,2), (-5,5), (-20,-19) ]:
         c = interactive(f, tup=(min, max), lis=[min, max])
-        nt.assert_equal(len(c.children), 2)
+        nt.assert_equal(len(c.children), 3)
         d = dict(
             cls=widgets.IntSlider,
             min=min,
@@ -174,7 +175,7 @@ def test_list_tuple_3_int():
         c = interactive(f, tup=(1,2,-1))
     for min, max, step in [ (0,2,1), (1,10,2), (1,100,2), (-5,5,4), (-100,-20,4) ]:
         c = interactive(f, tup=(min, max, step), lis=[min, max, step])
-        nt.assert_equal(len(c.children), 2)
+        nt.assert_equal(len(c.children), 3)
         d = dict(
             cls=widgets.IntSlider,
             min=min,
@@ -191,7 +192,7 @@ def test_list_tuple_2_float():
         c = interactive(f, tup=(0.5,-0.5))
     for min, max in [ (0.5, 1.5), (1.1,10.2), (1,2.2), (-5.,5), (-20,-19.) ]:
         c = interactive(f, tup=(min, max), lis=[min, max])
-        nt.assert_equal(len(c.children), 2)
+        nt.assert_equal(len(c.children), 3)
         d = dict(
             cls=widgets.FloatSlider,
             min=min,
@@ -210,7 +211,7 @@ def test_list_tuple_3_float():
         c = interactive(f, tup=(1,2.,-1.))
     for min, max, step in [ (0.,2,1), (1,10.,2), (1,100,2.), (-5.,5.,4), (-100,-20.,4.) ]:
         c = interactive(f, tup=(min, max, step), lis=[min, max, step])
-        nt.assert_equal(len(c.children), 2)
+        nt.assert_equal(len(c.children), 3)
         d = dict(
             cls=widgets.FloatSlider,
             min=min,
@@ -224,7 +225,7 @@ def test_list_tuple_str():
     values = ['hello', 'there', 'guy']
     first = values[0]
     c = interactive(f, tup=tuple(values), lis=list(values))
-    nt.assert_equal(len(c.children), 2)
+    nt.assert_equal(len(c.children), 3)
     d = dict(
         cls=widgets.Dropdown,
         value=first,
@@ -471,7 +472,7 @@ def foo(a='default'):
 
 def test_fixed():
     c = interactive(f, a=widgets.fixed(5), b='text')
-    nt.assert_equal(len(c.children), 1)
+    nt.assert_equal(len(c.children), 2)
     w = c.children[0]
     check_widget(w,
         cls=widgets.Text,