Skip to content

Commit d0bc730

Browse files
krassowskimeeseeksmachine
authored andcommitted
Backport PR ipython#14890: Fix interruption of %%time and %%debug magics
1 parent f4dc62a commit d0bc730

File tree

2 files changed

+47
-23
lines changed

2 files changed

+47
-23
lines changed

IPython/core/magics/execution.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,11 @@ def visit_For(self, node):
151151

152152
class Timer(timeit.Timer):
153153
"""Timer class that explicitly uses self.inner
154-
154+
155155
which is an undocumented implementation detail of CPython,
156156
not shared by PyPy.
157157
"""
158+
158159
# Timer.timeit copied from CPython 3.4.2
159160
def timeit(self, number=timeit.default_number):
160161
"""Time 'number' executions of the main statement.
@@ -192,7 +193,6 @@ def __init__(self, shell):
192193
@no_var_expand
193194
@line_cell_magic
194195
def prun(self, parameter_s='', cell=None):
195-
196196
"""Run a statement through the python code profiler.
197197
198198
**Usage, in line mode:**
@@ -993,7 +993,7 @@ def _run_with_debugger(
993993
# Stop iteration is raised on quit command
994994
pass
995995

996-
except:
996+
except Exception:
997997
etype, value, tb = sys.exc_info()
998998
# Skip three frames in the traceback: the %run one,
999999
# one inside bdb.py, and the command-line typed by the
@@ -1127,7 +1127,7 @@ def timeit(self, line='', cell=None, local_ns=None):
11271127
)
11281128
if stmt == "" and cell is None:
11291129
return
1130-
1130+
11311131
timefunc = timeit.default_timer
11321132
number = int(getattr(opts, "n", 0))
11331133
default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
@@ -1242,7 +1242,7 @@ def timeit(self, line='', cell=None, local_ns=None):
12421242
@needs_local_scope
12431243
@line_cell_magic
12441244
@output_can_be_silenced
1245-
def time(self,line='', cell=None, local_ns=None):
1245+
def time(self, line="", cell=None, local_ns=None):
12461246
"""Time execution of a Python statement or expression.
12471247
12481248
The CPU and wall clock times are printed, and the value of the
@@ -1257,13 +1257,19 @@ def time(self,line='', cell=None, local_ns=None):
12571257
- In cell mode, you can time the cell body (a directly
12581258
following statement raises an error).
12591259
1260-
This function provides very basic timing functionality. Use the timeit
1260+
This function provides very basic timing functionality. Use the timeit
12611261
magic for more control over the measurement.
12621262
12631263
.. versionchanged:: 7.3
12641264
User variables are no longer expanded,
12651265
the magic line is always left unmodified.
12661266
1267+
.. versionchanged:: 8.3
1268+
The time magic now correctly propagates system-exiting exceptions
1269+
(such as ``KeyboardInterrupt`` invoked when interrupting execution)
1270+
rather than just printing out the exception traceback.
1271+
The non-system-exception will still be caught as before.
1272+
12671273
Examples
12681274
--------
12691275
::
@@ -1304,10 +1310,10 @@ def time(self,line='', cell=None, local_ns=None):
13041310
Compiler : 0.78 s
13051311
"""
13061312
# fail immediately if the given expression can't be compiled
1307-
1313+
13081314
if line and cell:
13091315
raise UsageError("Can't use statement directly after '%%time'!")
1310-
1316+
13111317
if cell:
13121318
expr = self.shell.transform_cell(cell)
13131319
else:
@@ -1318,16 +1324,16 @@ def time(self,line='', cell=None, local_ns=None):
13181324

13191325
t0 = clock()
13201326
expr_ast = self.shell.compile.ast_parse(expr)
1321-
tp = clock()-t0
1327+
tp = clock() - t0
13221328

13231329
# Apply AST transformations
13241330
expr_ast = self.shell.transform_ast(expr_ast)
13251331

13261332
# Minimum time above which compilation time will be reported
13271333
tc_min = 0.1
13281334

1329-
expr_val=None
1330-
if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
1335+
expr_val = None
1336+
if len(expr_ast.body) == 1 and isinstance(expr_ast.body[0], ast.Expr):
13311337
mode = 'eval'
13321338
source = '<timed eval>'
13331339
expr_ast = ast.Expression(expr_ast.body[0].value)
@@ -1336,38 +1342,38 @@ def time(self,line='', cell=None, local_ns=None):
13361342
source = '<timed exec>'
13371343
# multi-line %%time case
13381344
if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr):
1339-
expr_val= expr_ast.body[-1]
1345+
expr_val = expr_ast.body[-1]
13401346
expr_ast = expr_ast.body[:-1]
13411347
expr_ast = Module(expr_ast, [])
13421348
expr_val = ast.Expression(expr_val.value)
13431349

13441350
t0 = clock()
13451351
code = self.shell.compile(expr_ast, source, mode)
1346-
tc = clock()-t0
1352+
tc = clock() - t0
13471353

13481354
# skew measurement as little as possible
13491355
glob = self.shell.user_ns
13501356
wtime = time.time
13511357
# time execution
13521358
wall_st = wtime()
1353-
if mode=='eval':
1359+
if mode == "eval":
13541360
st = clock2()
13551361
try:
13561362
out = eval(code, glob, local_ns)
1357-
except:
1363+
except Exception:
13581364
self.shell.showtraceback()
13591365
return
13601366
end = clock2()
13611367
else:
13621368
st = clock2()
13631369
try:
13641370
exec(code, glob, local_ns)
1365-
out=None
1371+
out = None
13661372
# multi-line %%time case
13671373
if expr_val is not None:
13681374
code_2 = self.shell.compile(expr_val, source, 'eval')
13691375
out = eval(code_2, glob, local_ns)
1370-
except:
1376+
except Exception:
13711377
self.shell.showtraceback()
13721378
return
13731379
end = clock2()
@@ -1597,14 +1603,15 @@ def parse_breakpoint(text, current_file):
15971603
return current_file, int(text)
15981604
else:
15991605
return text[:colon], int(text[colon+1:])
1600-
1606+
1607+
16011608
def _format_time(timespan, precision=3):
16021609
"""Formats the timespan in a human readable form"""
16031610

16041611
if timespan >= 60.0:
16051612
# we have more than a minute, format that in a human readable form
16061613
# Idea from http://snipplr.com/view/5713/
1607-
parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
1614+
parts = [("d", 60 * 60 * 24), ("h", 60 * 60), ("min", 60), ("s", 1)]
16081615
time = []
16091616
leftover = timespan
16101617
for suffix, length in parts:
@@ -1616,7 +1623,6 @@ def _format_time(timespan, precision=3):
16161623
break
16171624
return " ".join(time)
16181625

1619-
16201626
# Unfortunately characters outside of range(128) can cause problems in
16211627
# certain terminals.
16221628
# See bug: https://bugs.launchpad.net/ipython/+bug/348466
@@ -1630,7 +1636,7 @@ def _format_time(timespan, precision=3):
16301636
except:
16311637
pass
16321638
scaling = [1, 1e3, 1e6, 1e9]
1633-
1639+
16341640
if timespan > 0.0:
16351641
order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
16361642
else:

IPython/core/tests/test_magic.py

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@
1616
from threading import Thread
1717
from subprocess import CalledProcessError
1818
from textwrap import dedent
19-
from time import sleep
20-
from threading import Thread
2119
from unittest import TestCase, mock
2220

2321
import pytest
@@ -814,6 +812,16 @@ def test_timeit_invalid_return():
814812
with pytest.raises(SyntaxError):
815813
_ip.run_line_magic('timeit', 'return')
816814

815+
def test_timeit_raise_on_interrupt():
816+
ip = get_ipython()
817+
818+
with pytest.raises(KeyboardInterrupt):
819+
thread = Thread(target=_interrupt_after_1s)
820+
thread.start()
821+
ip.run_cell_magic("timeit", "", "from time import sleep; sleep(2)")
822+
thread.join()
823+
824+
817825
@dec.skipif(execution.profile is None)
818826
def test_prun_special_syntax():
819827
"Test %%prun with IPython special syntax"
@@ -1542,6 +1550,16 @@ def test_timeit_arguments():
15421550
_ip.run_line_magic("timeit", "-n1 -r1 a=('#')")
15431551

15441552

1553+
def test_time_raise_on_interrupt():
1554+
ip = get_ipython()
1555+
1556+
with pytest.raises(KeyboardInterrupt):
1557+
thread = Thread(target=_interrupt_after_1s)
1558+
thread.start()
1559+
ip.run_cell_magic("time", "", "from time import sleep; sleep(2)")
1560+
thread.join()
1561+
1562+
15451563
MINIMAL_LAZY_MAGIC = """
15461564
from IPython.core.magic import (
15471565
Magics,

0 commit comments

Comments
 (0)