Skip to content

Commit d9ce606

Browse files
skirpichevWolfgang Maier
andcommitted
gh-72902: improve Fraction(str) speed (don't use regexp's)
Co-authored-by: Wolfgang Maier <[email protected]>
1 parent 3e23047 commit d9ce606

File tree

2 files changed

+47
-43
lines changed

2 files changed

+47
-43
lines changed

Lib/fractions.py

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -53,20 +53,6 @@ def _hash_algorithm(numerator, denominator):
5353
result = hash_ if numerator >= 0 else -hash_
5454
return -2 if result == -1 else result
5555

56-
_RATIONAL_FORMAT = re.compile(r"""
57-
\A\s* # optional whitespace at the start,
58-
(?P<sign>[-+]?) # an optional sign, then
59-
(?=\d|\.\d) # lookahead for digit or .digit
60-
(?P<num>\d*|\d+(_\d+)*) # numerator (possibly empty)
61-
(?: # followed by
62-
(?:\s*/\s*(?P<denom>\d+(_\d+)*))? # an optional denominator
63-
| # or
64-
(?:\.(?P<decimal>\d*|\d+(_\d+)*))? # an optional fractional part
65-
(?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
66-
)
67-
\s*\z # and optional whitespace to finish
68-
""", re.VERBOSE | re.IGNORECASE)
69-
7056

7157
# Helpers for formatting
7258

@@ -238,11 +224,6 @@ def __new__(cls, numerator=0, denominator=None):
238224
self._denominator = 1
239225
return self
240226

241-
elif isinstance(numerator, numbers.Rational):
242-
self._numerator = numerator.numerator
243-
self._denominator = numerator.denominator
244-
return self
245-
246227
elif (isinstance(numerator, float) or
247228
(not isinstance(numerator, type) and
248229
hasattr(numerator, 'as_integer_ratio'))):
@@ -252,31 +233,52 @@ def __new__(cls, numerator=0, denominator=None):
252233

253234
elif isinstance(numerator, str):
254235
# Handle construction from strings.
255-
m = _RATIONAL_FORMAT.match(numerator)
256-
if m is None:
257-
raise ValueError('Invalid literal for Fraction: %r' %
258-
numerator)
259-
numerator = int(m.group('num') or '0')
260-
denom = m.group('denom')
261-
if denom:
262-
denominator = int(denom)
263-
else:
264-
denominator = 1
265-
decimal = m.group('decimal')
266-
if decimal:
267-
decimal = decimal.replace('_', '')
268-
scale = 10**len(decimal)
269-
numerator = numerator * scale + int(decimal)
270-
denominator *= scale
271-
exp = m.group('exp')
272-
if exp:
273-
exp = int(exp)
274-
if exp >= 0:
275-
numerator *= 10**exp
236+
fraction_literal = numerator
237+
num, _, denom = fraction_literal.partition('/')
238+
try:
239+
num = num.strip()
240+
denom = denom.strip()
241+
if num and denom and denom[0].isdigit():
242+
denominator = int(denom)
243+
numerator = int(num)
244+
elif num and not _:
245+
denominator = 1
246+
num, _, exp = num.replace('E', 'e').partition('e')
247+
if _ and not exp:
248+
raise ValueError
249+
num, _, decimal = num.partition('.')
250+
if decimal:
251+
if num and num[0] in ('+', '-'):
252+
sign = num[0] == '-'
253+
num = num[1:]
254+
else:
255+
sign = 0
256+
numerator = int(num or '0')
257+
decimal_len = len(decimal.replace('_', ''))
258+
decimal = int(decimal)
259+
scale = 10**decimal_len
260+
numerator = numerator*scale + decimal
261+
denominator *= scale
262+
if sign:
263+
numerator = -numerator
276264
else:
277-
denominator *= 10**-exp
278-
if m.group('sign') == '-':
279-
numerator = -numerator
265+
numerator = int(num)
266+
if exp:
267+
exp = int(exp)
268+
if exp >= 0:
269+
numerator *= 10**exp
270+
else:
271+
denominator *= 10**-exp
272+
else:
273+
raise ValueError
274+
except ValueError:
275+
raise ValueError('Invalid literal for Fraction: %r' %
276+
fraction_literal)
277+
278+
elif isinstance(numerator, numbers.Rational):
279+
self._numerator = numerator.numerator
280+
self._denominator = numerator.denominator
281+
return self
280282

281283
else:
282284
raise TypeError("argument should be a string or a Rational "
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Improve speed (x1.6-x2) of the :class:`~fractions.Fraction` constructor for
2+
string inputs.

0 commit comments

Comments
 (0)