Repository: ipython/ipython

Issue:
%%time magic prevents assignment
The `%%time` cell magic is preventing me from binding names. I found this in notebook code on Travis (py 3.6) and reproduced it locally (py 3.7) in notebook and in `ipython` at the terminal. It doesn't seem to apply to all cell magics:

```
Python 3.7.2 | packaged by conda-forge | (default, Mar 19 2019, 20:46:22)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.4.0 -- An enhanced Interactive Python. Type '?' for help.

In [1]: %%time
   ...: foo = "bar"
   ...:
   ...:
CPU times: user 3 µs, sys: 0 ns, total: 3 µs
Wall time: 6.91 µs

In [2]: foo
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-2-f1d2d2f924e9> in <module>
----> 1 foo

NameError: name 'foo' is not defined

In [3]: %%prun
   ...: baz = "qux"
   ...:
   ...:
         3 function calls in 0.000 seconds

   Ordered by: internal time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    0.000    0.000 {built-in method builtins.exec}
        1    0.000    0.000    0.000    0.000 <string>:2(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

In [4]: baz
Out[4]: 'qux'
```

Confirmed that 7.3.0 worked fine locally.


Discussion:
Looking through the PRs merged as part of 7.4.0, my best guess is this is an unexpected consequence of #11542 -- at least, I can see how this (and perhaps #11665, if `pos` existed as a list before the cell ran) would be caused by changing how scope is handled in cell magics. And I don't see anything else that looks like a likely cause.

But I don't know anything about the internals of magics: @Carreau, could this be the problem?

The workaround is just to stay on 7.3.0.
You are right that this is due to #11542 , I'll see if I can come up with a fix. 
`%%time` is used a lot, this affects many users & notebooks.
@Carreau or anyone - is it possible to do a patch release for this soon?
(for now I'll go back to v7.3 and tell others to do the same)
"%time" and "%%time" behave differently
![изображение](https://user-images.githubusercontent.com/966341/55426450-7f89d580-558d-11e9-92a6-0a8f82ab3a58.png)

![изображение](https://user-images.githubusercontent.com/966341/55426634-f45d0f80-558d-11e9-8800-d616ff271b5f.png)

![изображение](https://user-images.githubusercontent.com/966341/55426715-1b1b4600-558e-11e9-9875-95b75e0738e0.png)

but run_call_magic &&  run_line_magic  are identical
![изображение](https://user-images.githubusercontent.com/966341/55427243-3d619380-558f-11e9-889c-7e84eb4f5537.png)

It is not clear how it ever worked before
![изображение](https://user-images.githubusercontent.com/966341/55427748-5b7bc380-5590-11e9-9247-ff752bce0016.png)

By the way, in "%%timeit" the situation is exactly the same, but at least it behaves predictably

![изображение](https://user-images.githubusercontent.com/966341/55429330-e4482e80-5593-11e9-9ab8-782d29642138.png)

@wanick : I believe the `%%timeit` behavior was the same before 7.4.0 (and made sense to me: `timeit` does multiple runs -- which one should be the result?) Of course, there's no such ambiguity with `%%time`.
Well I'll fix it once ai have time. The thing is my $DAYJOB is not full
time open source anymore so IPython has effectively less than 20% of a
maintainer. And I prefer to use this time to review people code and be
friendly in a hope to get more people to help.

On Tue, Apr 2, 2019, 14:45 David W.H. Swenson <notifications@github.com>
wrote:

> @wanick <https://github.com/wanick> : I believe the %%timeit behavior was
> the same before 7.4.0 (and made sense to me: timeit does multiple runs --
> which one should be the result?) Of course, there's no such ambiguity with
> %%time.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/ipython/ipython/issues/11659#issuecomment-479217282>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAUez_RUQ7rT-3imrMP1uKBMFKMCYFo7ks5vc89bgaJpZM4cD5yv>
> .
>

@dwhswenson , True... I meant the same decorators and difference behavior 

@Carreau,  I solved this problem like this.

![изображение](https://user-images.githubusercontent.com/966341/55442056-ba9fff00-55b5-11e9-83a2-72812c5bc3d6.png)

If this is a solution, I can make a pull request

Thank you guys. It all makes sense to me, I'm just wondering whether I need to update/edit all my Jupyter notebook now, or you're planning to restore the expected/canonical behaviour.

Thanks!
We're planning to fix it as soon as we have time; it will likely make it to the 7.5 release that I'll try to make before end of month. 

Resolving patch:
diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py
--- a/IPython/core/interactiveshell.py
+++ b/IPython/core/interactiveshell.py
@@ -2345,7 +2345,7 @@ def run_cell_magic(self, magic_name, line, cell):
                 magic_arg_s = self.var_expand(line, stack_depth)
             kwargs = {}
             if getattr(fn, "needs_local_scope", False):
-                kwargs['local_ns'] = sys._getframe(stack_depth).f_locals
+                kwargs['local_ns'] = self.user_ns
 
             with self.builtin_trap:
                 args = (magic_arg_s, cell)
diff --git a/IPython/core/magics/execution.py b/IPython/core/magics/execution.py
--- a/IPython/core/magics/execution.py
+++ b/IPython/core/magics/execution.py
@@ -1127,8 +1127,8 @@ def timeit(self, line='', cell=None, local_ns=None):
         ns = {}
         glob = self.shell.user_ns
         # handles global vars with same name as local vars. We store them in conflict_globs.
-        if local_ns is not None:
-            conflict_globs = {}
+        conflict_globs = {}
+        if local_ns and cell is None:
             for var_name, var_val in glob.items():
                 if var_name in local_ns:
                     conflict_globs[var_name] = var_val
@@ -1154,9 +1154,8 @@ def timeit(self, line='', cell=None, local_ns=None):
         timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision)
 
         # Restore global vars from conflict_globs
-        if local_ns is not None:
-            if len(conflict_globs) > 0:
-                glob.update(conflict_globs)
+        if conflict_globs:
+           glob.update(conflict_globs)
                 
         if not quiet :
             # Check best timing is greater than zero to avoid a


Test patch:
diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py
--- a/IPython/core/tests/test_magic.py
+++ b/IPython/core/tests/test_magic.py
@@ -419,6 +419,16 @@ def test_time3():
                     "run = 0\n"
                     "run += 1")
 
+def test_time_local_ns():
+    """
+    Test that local_ns is actually global_ns when running a cell magic
+    """
+    ip = get_ipython()
+    ip.run_cell("%%time\n"
+                "myvar = 1")
+    nt.assert_equal(ip.user_ns['myvar'], 1)
+    del ip.user_ns['myvar']
+
 def test_doctest_mode():
     "Toggle doctest_mode twice, it should be a no-op and run without error"
     _ip.magic('doctest_mode')