From 4b298adfc3a9e40340b45871650a9eef2048f44f Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Sat, 19 Jul 2025 09:51:00 +0100 Subject: [PATCH 1/2] Add a failing test for stream performance --- tests/test_interactiveshell.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/test_interactiveshell.py b/tests/test_interactiveshell.py index b16882265b4..be07aa127a4 100644 --- a/tests/test_interactiveshell.py +++ b/tests/test_interactiveshell.py @@ -16,6 +16,7 @@ import shutil import sys import tempfile +import time import unittest import pytest from unittest import mock @@ -84,6 +85,15 @@ def test_run_cell_multiline(self): self.assertEqual(res.success, True) self.assertEqual(res.result, None) + def test_stream_performance(self): + """It should be fast to execute.""" + src = "for i in range(250_000): print(i)" + start = time.perf_counter() + ip.run_cell(src) + end = time.perf_counter() + duration = end - start + assert duration < 10 + def test_multiline_string_cells(self): "Code sprinkled with multiline strings should execute (GH-306)" ip.run_cell("tmp=0") From 0bd8e20cd255e0559f41da4a44fd910e50f4b446 Mon Sep 17 00:00:00 2001 From: krassowski <5832902+krassowski@users.noreply.github.com> Date: Sat, 19 Jul 2025 10:23:35 +0100 Subject: [PATCH 2/2] Fix the performance issue --- IPython/core/history.py | 2 +- IPython/core/interactiveshell.py | 4 ++-- IPython/core/magics/basic.py | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/IPython/core/history.py b/IPython/core/history.py index abb434407c9..929b472bd44 100644 --- a/IPython/core/history.py +++ b/IPython/core/history.py @@ -590,7 +590,7 @@ class HistoryOutput: output_type: typing.Literal[ "out_stream", "err_stream", "display_data", "execute_result" ] - bundle: typing.Dict[str, str] + bundle: typing.Dict[str, str | list[str]] class HistoryManager(HistoryAccessor): diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index ec85e2d3ad9..1176537079d 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -3063,11 +3063,11 @@ def write(data, *args, **kwargs): output_stream = outputs[-1] if output_stream is None: output_stream = HistoryOutput( - output_type=output_type, bundle={"stream": ""} + output_type=output_type, bundle={"stream": []} ) outputs_by_counter[execution_count].append(output_stream) - output_stream.bundle["stream"] += data # Append to existing stream + output_stream.bundle["stream"].append(data) # Append to existing stream return result stream.write = write diff --git a/IPython/core/magics/basic.py b/IPython/core/magics/basic.py index ec1ab532337..386707d236e 100644 --- a/IPython/core/magics/basic.py +++ b/IPython/core/magics/basic.py @@ -571,9 +571,11 @@ def notebook(self, s): for output in outputs[execution_count]: for mime_type, data in output.bundle.items(): if output.output_type == "out_stream": - cell.outputs.append(v4.new_output("stream", text=[data])) + text = data if isinstance(data, list) else [data] + cell.outputs.append(v4.new_output("stream", text=text)) elif output.output_type == "err_stream": - err_output = v4.new_output("stream", text=[data]) + text = data if isinstance(data, list) else [data] + err_output = v4.new_output("stream", text=text) err_output.name = "stderr" cell.outputs.append(err_output) elif output.output_type == "execute_result":