Skip to content

Commit 0a45f18

Browse files
committed
gh-137855: Improve import time of textwrap by lazy importing re modules
1 parent d948eaa commit 0a45f18

2 files changed

Lines changed: 62 additions & 44 deletions

File tree

Lib/test/test_textwrap.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010

1111
import unittest
1212

13+
from test.support import cpython_only
14+
from test.support.import_helper import ensure_lazy_imports
15+
1316
from textwrap import TextWrapper, wrap, fill, dedent, indent, shorten
1417

1518

@@ -1133,5 +1136,12 @@ def test_first_word_too_long_but_placeholder_fits(self):
11331136
self.check_shorten("Helloo", 5, "[...]")
11341137

11351138

1139+
class LazyImportTest(unittest.TestCase):
1140+
1141+
@cpython_only
1142+
def test_lazy_import(self):
1143+
ensure_lazy_imports("textwrap", {"re"})
1144+
1145+
11361146
if __name__ == '__main__':
11371147
unittest.main()

Lib/textwrap.py

Lines changed: 52 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# Copyright (C) 2002 Python Software Foundation.
66
# Written by Greg Ward <gward@python.net>
77

8-
import re
8+
lazy import re
99

1010
__all__ = ['TextWrapper', 'wrap', 'fill', 'dedent', 'indent', 'shorten']
1111

@@ -65,49 +65,56 @@ class TextWrapper:
6565

6666
unicode_whitespace_trans = dict.fromkeys(map(ord, _whitespace), ord(' '))
6767

68-
# This funky little regex is just the trick for splitting
69-
# text up into word-wrappable chunks. E.g.
70-
# "Hello there -- you goof-ball, use the -b option!"
71-
# splits into
72-
# Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option!
73-
# (after stripping out empty strings).
74-
word_punct = r'[\w!"\'&.,?]'
75-
letter = r'[^\d\W]'
76-
whitespace = r'[%s]' % re.escape(_whitespace)
77-
nowhitespace = '[^' + whitespace[1:]
78-
wordsep_re = re.compile(r'''
79-
( # any whitespace
80-
%(ws)s+
81-
| # em-dash between words
82-
(?<=%(wp)s) -{2,} (?=\w)
83-
| # word, possibly hyphenated
84-
%(nws)s+? (?:
85-
# hyphenated word
86-
-(?: (?<=%(lt)s{2}-) | (?<=%(lt)s-%(lt)s-))
87-
(?= %(lt)s -? %(lt)s)
88-
| # end of word
89-
(?=%(ws)s|\z)
90-
| # em-dash
91-
(?<=%(wp)s) (?=-{2,}\w)
92-
)
93-
)''' % {'wp': word_punct, 'lt': letter,
94-
'ws': whitespace, 'nws': nowhitespace},
95-
re.VERBOSE)
96-
del word_punct, letter, nowhitespace
97-
98-
# This less funky little regex just split on recognized spaces. E.g.
99-
# "Hello there -- you goof-ball, use the -b option!"
100-
# splits into
101-
# Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/
102-
wordsep_simple_re = re.compile(r'(%s+)' % whitespace)
103-
del whitespace
104-
105-
# XXX this is not locale- or charset-aware -- string.lowercase
106-
# is US-ASCII only (and therefore English-only)
107-
sentence_end_re = re.compile(r'[a-z]' # lowercase letter
108-
r'[\.\!\?]' # sentence-ending punct.
109-
r'[\"\']?' # optional end-of-quote
110-
r'\z') # end of chunk
68+
wordsep_re = None
69+
wordsep_simple_re = None
70+
sentence_end_re = None
71+
72+
@classmethod
73+
def _compile_wordseps(cls):
74+
"""Compile word-separator regexes on first use."""
75+
if cls.wordsep_re is not None:
76+
return
77+
# This funky little regex is just the trick for splitting
78+
# text up into word-wrappable chunks. E.g.
79+
# "Hello there -- you goof-ball, use the -b option!"
80+
# splits into
81+
# Hello/ /there/ /--/ /you/ /goof-/ball,/ /use/ /the/ /-b/ /option!
82+
# (after stripping out empty strings).
83+
word_punct = r'[\w!"\'&.,?]'
84+
letter = r'[^\d\W]'
85+
whitespace = r'[%s]' % re.escape(_whitespace)
86+
nowhitespace = '[^' + whitespace[1:]
87+
cls.wordsep_re = re.compile(r'''
88+
( # any whitespace
89+
%(ws)s+
90+
| # em-dash between words
91+
(?<=%(wp)s) -{2,} (?=\w)
92+
| # word, possibly hyphenated
93+
%(nws)s+? (?:
94+
# hyphenated word
95+
-(?: (?<=%(lt)s{2}-) | (?<=%(lt)s-%(lt)s-))
96+
(?= %(lt)s -? %(lt)s)
97+
| # end of word
98+
(?=%(ws)s|\z)
99+
| # em-dash
100+
(?<=%(wp)s) (?=-{2,}\w)
101+
)
102+
)''' % {'wp': word_punct, 'lt': letter,
103+
'ws': whitespace, 'nws': nowhitespace},
104+
re.VERBOSE)
105+
106+
# This less funky little regex just split on recognized spaces. E.g.
107+
# "Hello there -- you goof-ball, use the -b option!"
108+
# splits into
109+
# Hello/ /there/ /--/ /you/ /goof-ball,/ /use/ /the/ /-b/ /option!/
110+
cls.wordsep_simple_re = re.compile(r'(%s+)' % whitespace)
111+
112+
# XXX this is not locale- or charset-aware -- string.lowercase
113+
# is US-ASCII only (and therefore English-only)
114+
cls.sentence_end_re = re.compile(r'[a-z]' # lowercase letter
115+
r'[\.\!\?]' # sentence-ending punct.
116+
r'[\"\']?' # optional end-of-quote
117+
r'\z') # end of chunk
111118

112119
def __init__(self,
113120
width=70,
@@ -135,6 +142,7 @@ def __init__(self,
135142
self.tabsize = tabsize
136143
self.max_lines = max_lines
137144
self.placeholder = placeholder
145+
self._compile_wordseps()
138146

139147

140148
# -- Private methods -----------------------------------------------

0 commit comments

Comments
 (0)