Skip to content

Commit fec6717

Browse files
Optimise textwrap.dedent()
Co-authored-by: Marius Juston <[email protected]>
1 parent be59df8 commit fec6717

File tree

3 files changed

+22
-39
lines changed

3 files changed

+22
-39
lines changed

Lib/test/test_textwrap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -791,7 +791,7 @@ def test_dedent_only_whitespace(self):
791791

792792
# ASCII whitespace.
793793
text = "\f\n\r\t\v "
794-
expect = "\f\n\r\t\v "
794+
expect = "\n"
795795
self.assertEqual(expect, dedent(text))
796796

797797
# One newline.
@@ -801,7 +801,7 @@ def test_dedent_only_whitespace(self):
801801

802802
# Windows-style newlines.
803803
text = "\r\n"
804-
expect = "\r\n"
804+
expect = "\n"
805805
self.assertEqual(expect, dedent(text))
806806

807807
# Whitespace mixture.

Lib/textwrap.py

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -413,9 +413,6 @@ def shorten(text, width, **kwargs):
413413

414414
# -- Loosely related functionality -------------------------------------
415415

416-
_whitespace_only_re = re.compile('^[ \t]+$', re.MULTILINE)
417-
_leading_whitespace_re = re.compile('(^[ \t]*)(?:[^ \t\n])', re.MULTILINE)
418-
419416
def dedent(text):
420417
"""Remove any common leading whitespace from every line in `text`.
421418
@@ -429,42 +426,26 @@ def dedent(text):
429426
430427
Entirely blank lines are normalized to a newline character.
431428
"""
432-
# Look for the longest leading string of spaces and tabs common to
433-
# all lines.
434-
margin = None
435-
text = _whitespace_only_re.sub('', text)
436-
indents = _leading_whitespace_re.findall(text)
437-
for indent in indents:
438-
if margin is None:
439-
margin = indent
440-
441-
# Current line more deeply indented than previous winner:
442-
# no change (previous winner is still on top).
443-
elif indent.startswith(margin):
444-
pass
445-
446-
# Current line consistent with and no deeper than previous winner:
447-
# it's the new winner.
448-
elif margin.startswith(indent):
449-
margin = indent
450-
451-
# Find the largest common whitespace between current line and previous
452-
# winner.
453-
else:
454-
for i, (x, y) in enumerate(zip(margin, indent)):
455-
if x != y:
456-
margin = margin[:i]
457-
break
429+
if not text:
430+
return text
431+
432+
lines = text.split('\n')
433+
434+
non_blank_lines = [l for l in lines if l and not l.isspace()]
435+
if not non_blank_lines:
436+
return '\n'.join([l if l and not l.isspace() else '' for l in lines])
458437

459-
# sanity check (testing/debugging only)
460-
if 0 and margin:
461-
for line in text.split("\n"):
462-
assert not line or line.startswith(margin), \
463-
"line = %r, margin = %r" % (line, margin)
438+
# Get length of leading whitespace, inspired by ``os.path.commonprefix()``
439+
l1 = min(non_blank_lines)
440+
l2 = max(non_blank_lines)
441+
margin = 0
442+
for i, c in enumerate(l1):
443+
if c != l2[i] or c not in ' \t':
444+
break
445+
margin += 1
464446

465-
if margin:
466-
text = re.sub(r'(?m)^' + margin, '', text)
467-
return text
447+
return '\n'.join([l[margin:] if l and not l.isspace() else ''
448+
for l in lines])
468449

469450

470451
def indent(text, prefix, predicate=None):
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improved performance of :func: `textwrap.dedent` by ~2.4x.
2+
Patch by Adam Turner and Marius Juston.

0 commit comments

Comments
 (0)