From b722c2391f689eb409754bb4219011bbad985382 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 04:19:58 -0500 Subject: [PATCH 01/16] optimized dedent by 4x for larger files, added only_whitespace option if desired --- Lib/textwrap.py | 142 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 108 insertions(+), 34 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 1bf07aa46cad99..0f2e198f072a35 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -416,7 +416,7 @@ def shorten(text, width, **kwargs): _whitespace_only_re = re.compile('^[ \t]+$', re.MULTILINE) _leading_whitespace_re = re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE) -def dedent(text): +def dedent(text, only_whitespace = True): """Remove any common leading whitespace from every line in `text`. This can be used to make triple-quoted strings line up with the left @@ -427,44 +427,118 @@ def dedent(text): are not equal: the lines " hello" and "\\thello" are considered to have no common leading whitespace. + If `only_whitespace` is `True`, the leading whitespaces are removed from the text. Otherwise, all the common leading text is removed. + Entirely blank lines are normalized to a newline character. """ - # Look for the longest leading string of spaces and tabs common to - # all lines. - margin = None - text = _whitespace_only_re.sub('', text) - indents = _leading_whitespace_re.findall(text) - for indent in indents: - if margin is None: - margin = indent - - # Current line more deeply indented than previous winner: - # no change (previous winner is still on top). - elif indent.startswith(margin): - pass - - # Current line consistent with and no deeper than previous winner: - # it's the new winner. - elif margin.startswith(indent): - margin = indent - - # Find the largest common whitespace between current line and previous - # winner. + # Early return for empty input + if not text: + return text + + # Split into lines + lines = text.splitlines(True) + + # Fast path for single line - but make sure we still dedent! + if len(lines) == 1: + line = lines[0] + stripped = line.strip() + if not stripped: # Blank line + return "\n" if line.endswith("\n") else "" + + # Find leading whitespace for a single line + if only_whitespace: + i = 0 + while i < len(line) and line[i] in " \t": + i += 1 + if i > 0: # Has leading whitespace to remove + return line[i:] else: - for i, (x, y) in enumerate(zip(margin, indent)): - if x != y: - margin = margin[:i] - break + lead_size = len(line) - len(line.lstrip()) + if lead_size > 0: # Has leading whitespace to remove + return line[lead_size:] + return line # No whitespace to remove + + # Cache method lookups for faster access + _strip = str.strip + _startswith = str.startswith + _endswith = str.endswith + + # Find first two non-blank lines + non_blank = [] + for line in lines: + if _strip(line): + non_blank.append(line) + if len(non_blank) == 2: + break + + # All lines are blank + if not non_blank: + result = [] + append = result.append + for line in lines: + append("\n" if _endswith(line, "\n") else "") + return "".join(result) + + # Calculate margin length efficiently + if len(non_blank) == 1: + # Single non-blank line + line = non_blank[0] + if only_whitespace: + # Manually find leading whitespace (faster than regex) + i = 0 + line_len = len(line) + while i < line_len and line[i] in " \t": + i += 1 + margin_len = i + else: + # Use built-in lstrip for non-whitespace case + margin_len = len(line) - len(line.lstrip()) + else: + # Find common prefix of first two non-blank lines + a, b = non_blank + min_len = min(len(a), len(b)) + i = 0 - # sanity check (testing/debugging only) - if 0 and margin: - for line in text.split("\n"): - assert not line or line.startswith(margin), \ - "line = %r, margin = %r" % (line, margin) + if only_whitespace: + # Manual loop is faster than character-by-character comparison + while i < min_len and a[i] == b[i] and a[i] in " \t": + i += 1 + else: + while i < min_len and a[i] == b[i]: + i += 1 - if margin: - text = re.sub(r'(?m)^' + margin, '', text) - return text + margin_len = i + + # No margin to remove - return original with blank line normalization + if margin_len == 0: + result = [] + append = result.append + for line in lines: + if _strip(line): # Non-blank line + append(line) + else: # Blank line + append("\n" if _endswith(line, "\n") else "") + return "".join(result) + + # Get margin string once for repeated comparison + margin = non_blank[0][:margin_len] + + # Pre-allocate result list with a size hint for better memory efficiency + result = [] + append = result.append + + # Process all lines with optimized operations + for line in lines: + if not _strip(line): # Blank line (including whitespace-only lines) + append("\n" if _endswith(line, "\n") else "") + elif _startswith(line, margin): # Has margin + # Slice operation is very fast in Python + append(line[margin_len:]) + else: # No matching margin + append(line) + + # Single join is faster than incremental string building + return "".join(result) def indent(text, prefix, predicate=None): From 540b462f71a89e237e5d2d5e7885fe41241cbe26 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 04:25:11 -0500 Subject: [PATCH 02/16] removed necessary regex expression --- Lib/textwrap.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 0f2e198f072a35..1ed88bf9db114c 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -413,9 +413,6 @@ def shorten(text, width, **kwargs): # -- Loosely related functionality ------------------------------------- -_whitespace_only_re = re.compile('^[ \t]+$', re.MULTILINE) -_leading_whitespace_re = re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE) - def dedent(text, only_whitespace = True): """Remove any common leading whitespace from every line in `text`. From 21591182f00ddaf193095f763a42d1b808c7df12 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 04:37:33 -0500 Subject: [PATCH 03/16] added news entry --- .../next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst diff --git a/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst b/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst new file mode 100644 index 00000000000000..ef3787bcd4015a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst @@ -0,0 +1,2 @@ +:func: `textwrap.dedent` is now 4x faster than before for large inputs. Function now has the command argument to remove all common prefixes as well with `only_whitespaces` instead of just whitespaces. +Patch by Marius Juston. From 8cf215305e6abb4db3dee7b9eb2dc6e53110d022 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 04:43:37 -0500 Subject: [PATCH 04/16] updated news inline literals backticks --- .../next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst b/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst index ef3787bcd4015a..aece7b9f13632c 100644 --- a/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst +++ b/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst @@ -1,2 +1,2 @@ -:func: `textwrap.dedent` is now 4x faster than before for large inputs. Function now has the command argument to remove all common prefixes as well with `only_whitespaces` instead of just whitespaces. +Optimized :func: ``textwrap.dedent``. It is now 4x faster than before for large inputs. Function now has the command argument to remove all common prefixes as well with ``only_whitespace`` instead of just whitespaces. Patch by Marius Juston. From 7754af3f435b91a88e58dc8bb5d0f39cc3f86ec9 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 13:53:01 -0500 Subject: [PATCH 05/16] corrected dedent to pass test cases --- Lib/textwrap.py | 153 +++++++++++++----------------------------------- 1 file changed, 42 insertions(+), 111 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 1ed88bf9db114c..d6e7c9fb5ed622 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -413,129 +413,60 @@ def shorten(text, width, **kwargs): # -- Loosely related functionality ------------------------------------- -def dedent(text, only_whitespace = True): - """Remove any common leading whitespace from every line in `text`. - - This can be used to make triple-quoted strings line up with the left - edge of the display, while still presenting them in the source code - in indented form. - - Note that tabs and spaces are both treated as whitespace, but they - are not equal: the lines " hello" and "\\thello" are - considered to have no common leading whitespace. - - If `only_whitespace` is `True`, the leading whitespaces are removed from the text. Otherwise, all the common leading text is removed. +def dedent(text): + """ + Remove any common leading whitespace from every line in text. Entirely blank lines are normalized to a newline character. + Tabs and spaces are treated as distinct. + + This implementation uses os.path.commonprefix (implemented in C) + to compute the common margin of non-blank lines for maximal performance. """ - # Early return for empty input + # Fast paths for empty or simple text if not text: return text - # Split into lines - lines = text.splitlines(True) + if "\n" not in text: + return text # Single line has no dedent - # Fast path for single line - but make sure we still dedent! - if len(lines) == 1: - line = lines[0] - stripped = line.strip() - if not stripped: # Blank line - return "\n" if line.endswith("\n") else "" + # Split text into lines, preserving line endings + lines: List[str] = text.splitlines(keepends=True) + + # Process in a single pass to find: + # 1. Leading whitespace of non-blank lines + # 2. Whether a line has zero leading whitespace (optimization) + non_blank_whites = [] + has_zero_margin = False - # Find leading whitespace for a single line - if only_whitespace: - i = 0 - while i < len(line) and line[i] in " \t": - i += 1 - if i > 0: # Has leading whitespace to remove - return line[i:] - else: - lead_size = len(line) - len(line.lstrip()) - if lead_size > 0: # Has leading whitespace to remove - return line[lead_size:] - return line # No whitespace to remove - - # Cache method lookups for faster access - _strip = str.strip - _startswith = str.startswith - _endswith = str.endswith - - # Find first two non-blank lines - non_blank = [] for line in lines: - if _strip(line): - non_blank.append(line) - if len(non_blank) == 2: - break - - # All lines are blank - if not non_blank: - result = [] - append = result.append - for line in lines: - append("\n" if _endswith(line, "\n") else "") - return "".join(result) - - # Calculate margin length efficiently - if len(non_blank) == 1: - # Single non-blank line - line = non_blank[0] - if only_whitespace: - # Manually find leading whitespace (faster than regex) - i = 0 - line_len = len(line) - while i < line_len and line[i] in " \t": - i += 1 - margin_len = i - else: - # Use built-in lstrip for non-whitespace case - margin_len = len(line) - len(line.lstrip()) + stripped = line.strip() + if stripped: # Non-blank line + leading = line[:len(line) - len(line.lstrip())] + non_blank_whites.append(leading) + # Early detection of zero margin case + if not leading: + has_zero_margin = True + break # No need to check more lines + + # If all lines are blank, normalize them + if not non_blank_whites: + # Preallocate result list + return "".join(["\n" if line.endswith("\n") else "" for line in lines]) + + # Skip commonprefix calculation if we already know there's no margin + if has_zero_margin: + margin_len = 0 else: - # Find common prefix of first two non-blank lines - a, b = non_blank - min_len = min(len(a), len(b)) - i = 0 - - if only_whitespace: - # Manual loop is faster than character-by-character comparison - while i < min_len and a[i] == b[i] and a[i] in " \t": - i += 1 - else: - while i < min_len and a[i] == b[i]: - i += 1 - - margin_len = i + common = os.path.commonprefix(non_blank_whites) + margin_len = len(common) - # No margin to remove - return original with blank line normalization + # No common margin case - just normalize blank lines if margin_len == 0: - result = [] - append = result.append - for line in lines: - if _strip(line): # Non-blank line - append(line) - else: # Blank line - append("\n" if _endswith(line, "\n") else "") - return "".join(result) - - # Get margin string once for repeated comparison - margin = non_blank[0][:margin_len] - - # Pre-allocate result list with a size hint for better memory efficiency - result = [] - append = result.append - - # Process all lines with optimized operations - for line in lines: - if not _strip(line): # Blank line (including whitespace-only lines) - append("\n" if _endswith(line, "\n") else "") - elif _startswith(line, margin): # Has margin - # Slice operation is very fast in Python - append(line[margin_len:]) - else: # No matching margin - append(line) - - # Single join is faster than incremental string building - return "".join(result) + return "".join([line if line.strip() else "\n" if line.endswith("\n") else "" for line in lines]) + + # Apply margin removal (most common case) with minimal operations + return "".join([line[margin_len:] if line.strip() else "\n" if line.endswith("\n") else "" for line in lines]) def indent(text, prefix, predicate=None): From 76f231a8e3b13d06e7e4767e00029a35e36e318c Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 13:57:11 -0500 Subject: [PATCH 06/16] added missing os dependencie --- Lib/textwrap.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index d6e7c9fb5ed622..ff5d5694002d87 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -6,6 +6,7 @@ # Written by Greg Ward import re +import os __all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent', 'shorten'] @@ -431,7 +432,7 @@ def dedent(text): return text # Single line has no dedent # Split text into lines, preserving line endings - lines: List[str] = text.splitlines(keepends=True) + lines = text.splitlines(keepends=True) # Process in a single pass to find: # 1. Leading whitespace of non-blank lines From 8dce5ab5ef9e996ce5dd5d52867bb844cf9d89b7 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 14:00:28 -0500 Subject: [PATCH 07/16] reset what the current doc was --- Lib/textwrap.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index ff5d5694002d87..0846b53c71aa90 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -415,14 +415,17 @@ def shorten(text, width, **kwargs): # -- Loosely related functionality ------------------------------------- def dedent(text): - """ - Remove any common leading whitespace from every line in text. + """Remove any common leading whitespace from every line in `text`. - Entirely blank lines are normalized to a newline character. - Tabs and spaces are treated as distinct. + This can be used to make triple-quoted strings line up with the left + edge of the display, while still presenting them in the source code + in indented form. - This implementation uses os.path.commonprefix (implemented in C) - to compute the common margin of non-blank lines for maximal performance. + Note that tabs and spaces are both treated as whitespace, but they + are not equal: the lines " hello" and "\\thello" are + considered to have no common leading whitespace. + + Entirely blank lines are normalized to a newline character. """ # Fast paths for empty or simple text if not text: From 520f25ff1b99682944b08b87ff15145570c1fbe9 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 16:45:47 -0500 Subject: [PATCH 08/16] much simpler implementation, using fileter and common prefix for fast checking of common prefixes for each line --- Lib/textwrap.py | 47 +++++-------------- ...-03-27-04-35-17.gh-issue-131792.UtGg3O.rst | 3 +- 2 files changed, 13 insertions(+), 37 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 0846b53c71aa90..0eb3173f8a289d 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -434,43 +434,20 @@ def dedent(text): if "\n" not in text: return text # Single line has no dedent - # Split text into lines, preserving line endings - lines = text.splitlines(keepends=True) - - # Process in a single pass to find: - # 1. Leading whitespace of non-blank lines - # 2. Whether a line has zero leading whitespace (optimization) - non_blank_whites = [] - has_zero_margin = False - - for line in lines: - stripped = line.strip() - if stripped: # Non-blank line - leading = line[:len(line) - len(line.lstrip())] - non_blank_whites.append(leading) - # Early detection of zero margin case - if not leading: - has_zero_margin = True - break # No need to check more lines - - # If all lines are blank, normalize them - if not non_blank_whites: - # Preallocate result list - return "".join(["\n" if line.endswith("\n") else "" for line in lines]) - - # Skip commonprefix calculation if we already know there's no margin - if has_zero_margin: - margin_len = 0 - else: - common = os.path.commonprefix(non_blank_whites) - margin_len = len(common) - - # No common margin case - just normalize blank lines - if margin_len == 0: - return "".join([line if line.strip() else "\n" if line.endswith("\n") else "" for line in lines]) + lines = text.split("\n") + + splitting = os.path.commonprefix(tuple(filter(lambda x: x.lstrip(), lines))) + + margin_len = 0 + + for split in splitting: + if ' \t' in split : + margin_len += 1 + else: + break # Apply margin removal (most common case) with minimal operations - return "".join([line[margin_len:] if line.strip() else "\n" if line.endswith("\n") else "" for line in lines]) + return "\n".join([line[margin_len:] if line.strip() else "\n" if line.endswith("\n") else "" for line in lines]) def indent(text, prefix, predicate=None): diff --git a/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst b/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst index aece7b9f13632c..3c531b86b018ac 100644 --- a/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst +++ b/Misc/NEWS.d/next/Library/2025-03-27-04-35-17.gh-issue-131792.UtGg3O.rst @@ -1,2 +1 @@ -Optimized :func: ``textwrap.dedent``. It is now 4x faster than before for large inputs. Function now has the command argument to remove all common prefixes as well with ``only_whitespace`` instead of just whitespaces. -Patch by Marius Juston. +Optimized :func:`textwrap.dedent`. It is now 2x faster than before for large inputs. From e703e0ddf519427ea1499c469ddbf6107c37176a Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 16:52:52 -0500 Subject: [PATCH 09/16] small mistake for single line --- Lib/textwrap.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 0eb3173f8a289d..86089a2c2fbb3f 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -431,9 +431,6 @@ def dedent(text): if not text: return text - if "\n" not in text: - return text # Single line has no dedent - lines = text.split("\n") splitting = os.path.commonprefix(tuple(filter(lambda x: x.lstrip(), lines))) From 159e3633a5d560648e41176a878634f4afbdc0ef Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 17:16:03 -0500 Subject: [PATCH 10/16] forgot to invert the compairson --- Lib/textwrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 86089a2c2fbb3f..60e4ee77c1862d 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -438,7 +438,7 @@ def dedent(text): margin_len = 0 for split in splitting: - if ' \t' in split : + if split in ' \t': margin_len += 1 else: break From 1c2678ea3d91cd8edf66fecb9099938f1fe674fc Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 18:38:19 -0500 Subject: [PATCH 11/16] minor optimization, using lstrip and more concise margin_len computation --- Lib/textwrap.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 86089a2c2fbb3f..8ff8c39178b630 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -434,17 +434,9 @@ def dedent(text): lines = text.split("\n") splitting = os.path.commonprefix(tuple(filter(lambda x: x.lstrip(), lines))) + margin_len = len(splitting) - len(splitting.lstrip()) - margin_len = 0 - - for split in splitting: - if ' \t' in split : - margin_len += 1 - else: - break - - # Apply margin removal (most common case) with minimal operations - return "\n".join([line[margin_len:] if line.strip() else "\n" if line.endswith("\n") else "" for line in lines]) + return "\n".join([line[margin_len:] if line.lstrip() else "\n" if line.endswith("\n") else "" for line in lines]) def indent(text, prefix, predicate=None): From c7cd5ceea455517ca0a416e3a0b744cb9a6e424d Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Thu, 27 Mar 2025 18:46:24 -0500 Subject: [PATCH 12/16] updated variable name --- Lib/textwrap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 8ff8c39178b630..e08d468dcbd7f3 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -433,8 +433,8 @@ def dedent(text): lines = text.split("\n") - splitting = os.path.commonprefix(tuple(filter(lambda x: x.lstrip(), lines))) - margin_len = len(splitting) - len(splitting.lstrip()) + margin = os.path.commonprefix(tuple(filter(lambda x: x.lstrip(), lines))) + margin_len = len(margin) - len(margin.lstrip()) return "\n".join([line[margin_len:] if line.lstrip() else "\n" if line.endswith("\n") else "" for line in lines]) From 199d23734ff46629b261b0066871eb7c31cf205b Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Fri, 28 Mar 2025 11:58:49 -0500 Subject: [PATCH 13/16] minor faster filtering using list comprehension --- Lib/textwrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index e08d468dcbd7f3..ae99fb5de9544d 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -433,7 +433,7 @@ def dedent(text): lines = text.split("\n") - margin = os.path.commonprefix(tuple(filter(lambda x: x.lstrip(), lines))) + margin = os.path.commonprefix([line for line in lines if line.lstrip()]) margin_len = len(margin) - len(margin.lstrip()) return "\n".join([line[margin_len:] if line.lstrip() else "\n" if line.endswith("\n") else "" for line in lines]) From 27fcf53e822e636f4807ed0cbcfff0f4f8a88c29 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Fri, 28 Mar 2025 12:09:41 -0500 Subject: [PATCH 14/16] small speedup by removing ends with --- Lib/textwrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index ae99fb5de9544d..45476897f48c78 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -436,7 +436,7 @@ def dedent(text): margin = os.path.commonprefix([line for line in lines if line.lstrip()]) margin_len = len(margin) - len(margin.lstrip()) - return "\n".join([line[margin_len:] if line.lstrip() else "\n" if line.endswith("\n") else "" for line in lines]) + return "\n".join([line[margin_len:] if line.lstrip() else "\n" if line and line[-1] == "\n" else "" for line in lines]) def indent(text, prefix, predicate=None): From 1379566463b1746a6ebc98fd548e9c8b92c8e0a4 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Fri, 28 Mar 2025 12:23:05 -0500 Subject: [PATCH 15/16] strip seems to give an extremely minor performance boost over lstrip --- Lib/textwrap.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 45476897f48c78..9f1978586bae72 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -427,16 +427,15 @@ def dedent(text): Entirely blank lines are normalized to a newline character. """ - # Fast paths for empty or simple text if not text: return text lines = text.split("\n") - margin = os.path.commonprefix([line for line in lines if line.lstrip()]) + margin = os.path.commonprefix([line for line in lines if line.strip()]) margin_len = len(margin) - len(margin.lstrip()) - return "\n".join([line[margin_len:] if line.lstrip() else "\n" if line and line[-1] == "\n" else "" for line in lines]) + return "\n".join([line[margin_len:] if line.strip() else "\n" if line and line[-1] == "\n" else "" for line in lines]) def indent(text, prefix, predicate=None): From b71240dabf4ec99a1d28146928ab661bfb562f46 Mon Sep 17 00:00:00 2001 From: Marius Juston Date: Sun, 30 Mar 2025 14:33:48 -0500 Subject: [PATCH 16/16] fixed unecessary end line operator for empty --- Lib/textwrap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/textwrap.py b/Lib/textwrap.py index 9f1978586bae72..38ab6256b8f028 100644 --- a/Lib/textwrap.py +++ b/Lib/textwrap.py @@ -435,7 +435,7 @@ def dedent(text): margin = os.path.commonprefix([line for line in lines if line.strip()]) margin_len = len(margin) - len(margin.lstrip()) - return "\n".join([line[margin_len:] if line.strip() else "\n" if line and line[-1] == "\n" else "" for line in lines]) + return "\n".join([line[margin_len:] if line.strip() else "" for line in lines]) def indent(text, prefix, predicate=None):