Repository: ipython/ipython

Issue:
`?` may generate hundreds of cell 
By mistake I have executed a cell like 

```
for i in range(3):
    x= range?
```

but with ~70 instead of 3
which generated 70 code cell with just `x= range` in it...
it was _really_ painfull to clean, it would be nice to prevent something like that



Discussion:
Ah, that's from a convenience I added for the console - if you do `x=range?`, it will show the help and put `x=range` at the next prompt so you can keep typing. It seems the notebook interprets that as making a new cell.

That 'next prompt' action probably shouldn't happen for multiline cells. It should probably also be disabled for the notebook, but I don't know how much the kernel knows about the frontend it's talking to.

Does the `%loadpy` function also use next_prompt ?

Yes, I believe it does.

this goes back to the IPython.zmq.zmqshell.ZMQInteractiveShell.set_next_input function.  It is being called on each iteration of the loop with the string: "    x=range?"

What is this function for and any is it called each time ? is triggered?  I did a quick grin and this set_next_input is called throughout the codebase.  The notebook current creates a new cell with the contents of the payload.  I didn't add any of this code, but would like to understand the logic behind all of this.  @fperez, @minrk ?

This is how things like `%loadpy` work, for initializing the contents of the next input (cell).  It should probably be implemented in a way that multiple calls within a single cell only result in one actual event.  The most logical model is probably for each call to clobber the previous one, so that only the last call within a given execution actually has an effect.

OK, so this sounds like an issue with set_next_input itself, not the
notebook.  @takluyver can you investigate?

On Mon, Jan 30, 2012 at 4:29 PM, Min RK
reply@reply.github.com
wrote:

> This is how things like `%loadpy` work, for initializing the contents of the next input (cell).  It should probably be implemented in a way that multiple calls within a single cell only result in one actual event.  The most logical model is probably for each call to clobber the previous one, so that only the last call within a given execution actually has an effect.
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/ipython/ipython/issues/1349#issuecomment-3731078

## 

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

Min's suggestion sounds sensible - this is how the terminal and the Qt console already work: set_next_cell sets an internal value, which is read when we produce the next prompt. I'll look into it, but it will be tomorrow, so if anyone else wants to take a crack at it in the meantime, go for it.


Resolving patch:
diff --git a/IPython/core/payload.py b/IPython/core/payload.py
--- a/IPython/core/payload.py
+++ b/IPython/core/payload.py
@@ -29,9 +29,23 @@ class PayloadManager(Configurable):
 
     _payload = List([])
 
-    def write_payload(self, data):
+    def write_payload(self, data, single=True):
+        """Include or update the specified `data` payload in the PayloadManager.
+
+        If a previous payload with the same source exists and `single` is True,
+        it will be overwritten with the new one.
+        """
+
         if not isinstance(data, dict):
             raise TypeError('Each payload write must be a dict, got: %r' % data)
+
+        if single and 'source' in data:
+            source = data['source']
+            for i, pl in enumerate(self._payload):
+                if 'source' in pl and pl['source'] == source:
+                    self._payload[i] = data
+                    return
+
         self._payload.append(data)
 
     def read_payload(self):


Test patch:
diff --git a/IPython/kernel/tests/test_message_spec.py b/IPython/kernel/tests/test_message_spec.py
--- a/IPython/kernel/tests/test_message_spec.py
+++ b/IPython/kernel/tests/test_message_spec.py
@@ -406,6 +406,15 @@ def test_kernel_info_request():
     validate_message(reply, 'kernel_info_reply', msg_id)
 
 
+def test_single_payload():
+    flush_channels()
+    msg_id, reply = execute(code="for i in range(3):\n"+
+                                 "   x=range?\n")
+    payload = reply['payload']
+    next_input_pls = [pl for pl in payload if pl["source"] == "set_next_input"]
+    nt.assert_equal(len(next_input_pls), 1)
+
+
 # IOPub channel