You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
textwrap should treat Unicode em-dash like ASCII em-dash #74865
The textwrap module goes to great lengths to "do the right thing" when it finds the ASCII simulation of an em-dash (two or more consecutive hyphens), but it does nothing to recognize and similarly treat true (Unicode) em-dashes (aka '\N{EM DASH}', '\u2014', or U+2014). Real em-dashes should get at least as good a treatment as simulated em-dashes.
This seems sensible to me (I haven't looked at the PR, I'm talking about adding the support). When textwrap was written python was pretty ascii oriented, so it is not too much of a surprise that unicode em dashes were not supported.
jonathaneunice commented on Jun 15, 2017
jonathaneunicemannequin
MannequinAuthor
More actions
Agreed. It makes great sense that textwrap started as highly ASCII-centric. But in the Python 3, Unicode-friendly era, ASCII-biased isn't where we should leave things.
Agreed. It makes great sense that textwrap started as highly ASCII-centric. But in the Python 3, Unicode-friendly era, ASCII-biased isn't where we should leave things.
But I am not sure some core developer love textwrap and Unicode enough to implement it.
It can be implemented in 3rd party package before adding it in stdlib.
Then, is U+2014 really important to implement even though we can not implement UAX#14 in foreseeable future?
It doesn't make sense to me.
I fully agree with @methane.
IMO, textwrap's niche should be English docstrings: predominantly-ASCII blocks of text intended to be displayed in a monospace font.
To get out of that niche, it would not only need correct line wrapping, but also for example support for double-width (蠎🐍) or zero-width characters. The scope could be extended practically infinitely. A third-party package on PyPI would be a much better place for this.
Adding individual bits and pieces, like em-dash support, will either break output compatibility with or grow the already large number of TextWrapper options. I don't think it's worth it.
I think an escape hatch could be to allow the caller to supply a function to calculate string length.
This would also fix my use case where I want to wrap strings which contain colour sequences :)
eg something like so:
--- /opt/local/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/textwrap.py 2025-06-08 19:44:57
+++ mytextwrap.py 2025-08-30 13:53:33
@@ -61,6 +61,8 @@
Truncate wrapped lines.
placeholder (default: ' [...]')
Append to the last line of truncated text.
+ lenfunc (default: len)
+ Function used to determine length of a given piece of text.
"""
unicode_whitespace_trans = {}
@@ -125,7 +127,8 @@
tabsize=8,
*,
max_lines=None,
- placeholder=' [...]'):
+ placeholder=' [...]',
+ lenfunc = len):
self.width = width
self.initial_indent = initial_indent
self.subsequent_indent = subsequent_indent
@@ -138,6 +141,7 @@
self.tabsize = tabsize
self.max_lines = max_lines
self.placeholder = placeholder
+ self.lenfunc = lenfunc
# -- Private methods -----------------------------------------------
@@ -217,7 +221,7 @@
if self.break_long_words:
end = space_left
chunk = reversed_chunks[-1]
- if self.break_on_hyphens and len(chunk) > space_left:
+ if self.break_on_hyphens and self.lenfunc(chunk) > space_left:
# break after last hyphen, but only if there are
# non-hyphens before it
hyphen = chunk.rfind('-', 0, space_left)
@@ -259,7 +263,7 @@
indent = self.subsequent_indent
else:
indent = self.initial_indent
- if len(indent) + len(self.placeholder.lstrip()) > self.width:
+ if self.lenfunc(indent) + self.lenfunc(self.placeholder.lstrip()) > self.width:
raise ValueError("placeholder too large for max width")
# Arrange in reverse order so items can be efficiently popped
@@ -280,7 +284,7 @@
indent = self.initial_indent
# Maximum width for this line.
- width = self.width - len(indent)
+ width = self.width - self.lenfunc(indent)
# First chunk on line is whitespace -- drop it, unless this
# is the very beginning of the text (ie. no lines started yet).
@@ -288,7 +292,7 @@
del chunks[-1]
while chunks:
- l = len(chunks[-1])
+ l = self.lenfunc(chunks[-1])
# Can at least squeeze this chunk onto the current line.
if cur_len + l <= width:
@@ -301,13 +305,13 @@
# The current line is full, and the next chunk is too big to
# fit on *any* line (not just this one).
- if chunks and len(chunks[-1]) > width:
+ if chunks and self.lenfunc(chunks[-1]) > width:
self._handle_long_word(chunks, cur_line, cur_len, width)
cur_len = sum(map(len, cur_line))
# If the last chunk on this line is all whitespace, drop it.
if self.drop_whitespace and cur_line and cur_line[-1].strip() == '':
- cur_len -= len(cur_line[-1])
+ cur_len -= self.lenfunc(cur_line[-1])
del cur_line[-1]
if cur_line:
@@ -323,16 +327,16 @@
else:
while cur_line:
if (cur_line[-1].strip() and
- cur_len + len(self.placeholder) <= width):
+ cur_len + self.lenfunc(self.placeholder) <= width):
cur_line.append(self.placeholder)
lines.append(indent + ''.join(cur_line))
break
- cur_len -= len(cur_line[-1])
+ cur_len -= self.lenfunc(cur_line[-1])
del cur_line[-1]
else:
if lines:
prev_line = lines[-1].rstrip()
- if (len(prev_line) + len(self.placeholder) <=
+ if (self.lenfunc(prev_line) + self.lenfunc(self.placeholder) <=
self.width):
lines[-1] = prev_line + self.placeholder
break
Activity
jonathaneunice commentedon Jun 15, 2017
The textwrap module goes to great lengths to "do the right thing" when it finds the ASCII simulation of an em-dash (two or more consecutive hyphens), but it does nothing to recognize and similarly treat true (Unicode) em-dashes (aka '\N{EM DASH}', '\u2014', or U+2014). Real em-dashes should get at least as good a treatment as simulated em-dashes.
bitdancer commentedon Jun 15, 2017
This seems sensible to me (I haven't looked at the PR, I'm talking about adding the support). When textwrap was written python was pretty ascii oriented, so it is not too much of a surprise that unicode em dashes were not supported.
jonathaneunice commentedon Jun 15, 2017
Agreed. It makes great sense that textwrap started as highly ASCII-centric. But in the Python 3, Unicode-friendly era, ASCII-biased isn't where we should leave things.
methane commentedon Oct 21, 2020
It needs Unicode experts. If we support Unicode, we should implemente UAX #14.
http://www.unicode.org/reports/tr14/tr14-45.html
But I am not sure some core developer love textwrap and Unicode enough to implement it.
It can be implemented in 3rd party package before adding it in stdlib.
Then, is U+2014 really important to implement even though we can not implement UAX#14 in foreseeable future?
It doesn't make sense to me.
encukou commentedon Jan 3, 2025
I fully agree with @methane.
IMO,
textwrap's niche should be English docstrings: predominantly-ASCII blocks of text intended to be displayed in a monospace font.To get out of that niche, it would not only need correct line wrapping, but also for example support for double-width
(蠎🐍)or zero-width characters. The scope could be extended practically infinitely. A third-party package on PyPI would be a much better place for this.Adding individual bits and pieces, like em-dash support, will either break output compatibility with or grow the already large number of
TextWrapperoptions. I don't think it's worth it.DanielO commentedon Aug 30, 2025
I think an escape hatch could be to allow the caller to supply a function to calculate string length.
This would also fix my use case where I want to wrap strings which contain colour sequences :)
eg something like so: