Issue #5812: Make Fraction('1e6') valid. The Fraction constructor now

accepts all strings accepted by the float and Decimal constructors,
with the exception of strings representing NaNs or infinities.
This commit is contained in:
Mark Dickinson 2009-04-22 17:50:21 +00:00
parent 937491d1a9
commit cf63f2fb88
4 changed files with 47 additions and 33 deletions

View File

@ -25,21 +25,18 @@ another rational number, or from a string.
:exc:`ZeroDivisionError`. The second version requires that
*other_fraction* is an instance of :class:`numbers.Rational` and
returns an :class:`Fraction` instance with the same value. The
last version of the constructor expects a string
instance in one of two possible forms. The first form is::
last version of the constructor expects a string instance. The
usual form for this string is::
[sign] numerator ['/' denominator]
where the optional ``sign`` may be either '+' or '-' and
``numerator`` and ``denominator`` (if present) are strings of
decimal digits. The second permitted form is that of a number
containing a decimal point::
[sign] integer '.' [fraction] | [sign] '.' fraction
where ``integer`` and ``fraction`` are strings of digits. In
either form the input string may also have leading and/or trailing
whitespace. Here are some examples::
decimal digits. In addition, any string that represents a finite
value and is accepted by the :class:`float` constructor is also
accepted by the :class:`Fraction` constructor. In either form the
input string may also have leading and/or trailing whitespace.
Here are some examples::
>>> from fractions import Fraction
>>> Fraction(16, -10)
@ -57,6 +54,8 @@ another rational number, or from a string.
Fraction(1414213, 1000000)
>>> Fraction('-.125')
Fraction(-1, 8)
>>> Fraction('7e-6')
Fraction(7, 1000000)
The :class:`Fraction` class inherits from the abstract base class

View File

@ -28,13 +28,14 @@ _RATIONAL_FORMAT = re.compile(r"""
(?P<sign>[-+]?) # an optional sign, then
(?=\d|\.\d) # lookahead for digit or .digit
(?P<num>\d*) # numerator (possibly empty)
(?: # followed by an optional
/(?P<denom>\d+) # / and denominator
(?: # followed by
(?:/(?P<denom>\d+))? # an optional denominator
| # or
\.(?P<decimal>\d*) # decimal point and fractional part
)?
(?:\.(?P<decimal>\d*))? # an optional fractional part
(?:E(?P<exp>[-+]?\d+))? # and optional exponent
)
\s*\Z # and optional whitespace to finish
""", re.VERBOSE)
""", re.VERBOSE | re.IGNORECASE)
class Fraction(numbers.Rational):
@ -65,22 +66,28 @@ class Fraction(numbers.Rational):
if not isinstance(numerator, int) and denominator == 1:
if isinstance(numerator, str):
# Handle construction from strings.
input = numerator
m = _RATIONAL_FORMAT.match(input)
m = _RATIONAL_FORMAT.match(numerator)
if m is None:
raise ValueError('Invalid literal for Fraction: %r' % input)
numerator = m.group('num')
decimal = m.group('decimal')
if decimal:
# The literal is a decimal number.
numerator = int(numerator + decimal)
denominator = 10**len(decimal)
raise ValueError('Invalid literal for Fraction: %r' %
numerator)
numerator = int(m.group('num') or '0')
denom = m.group('denom')
if denom:
denominator = int(denom)
else:
# The literal is an integer or fraction.
numerator = int(numerator)
# Default denominator to 1.
denominator = int(m.group('denom') or 1)
denominator = 1
decimal = m.group('decimal')
if decimal:
scale = 10**len(decimal)
numerator = numerator * scale + int(decimal)
denominator *= scale
exp = m.group('exp')
if exp:
exp = int(exp)
if exp >= 0:
numerator *= 10**exp
else:
denominator *= 10**-exp
if m.group('sign') == '-':
numerator = -numerator

View File

@ -78,6 +78,11 @@ class FractionTest(unittest.TestCase):
self.assertEquals((-16, 5), _components(F(" -3.2 ")))
self.assertEquals((-3, 1), _components(F(" -3. ")))
self.assertEquals((3, 5), _components(F(" .6 ")))
self.assertEquals((1, 3125), _components(F("32.e-5")))
self.assertEquals((1000000, 1), _components(F("1E+06")))
self.assertEquals((-12300, 1), _components(F("-1.23e4")))
self.assertEquals((0, 1), _components(F(" .0e+0\t")))
self.assertEquals((0, 1), _components(F("-0.000e0")))
self.assertRaisesMessage(
ZeroDivisionError, "Fraction(3, 0)",
@ -85,6 +90,9 @@ class FractionTest(unittest.TestCase):
self.assertRaisesMessage(
ValueError, "Invalid literal for Fraction: '3/'",
F, "3/")
self.assertRaisesMessage(
ValueError, "Invalid literal for Fraction: '/2'",
F, "/2")
self.assertRaisesMessage(
ValueError, "Invalid literal for Fraction: '3 /2'",
F, "3 /2")
@ -100,10 +108,6 @@ class FractionTest(unittest.TestCase):
# Avoid treating '.' as a regex special character.
ValueError, "Invalid literal for Fraction: '3a2'",
F, "3a2")
self.assertRaisesMessage(
# Only parse ordinary decimals, not scientific form.
ValueError, "Invalid literal for Fraction: '3.2e4'",
F, "3.2e4")
self.assertRaisesMessage(
# Don't accept combinations of decimals and rationals.
ValueError, "Invalid literal for Fraction: '3/7.2'",

View File

@ -75,6 +75,10 @@ Core and Builtins
Library
-------
- Issue #5812: Fraction('1e6') is valid: more generally, any string
that's valid for float() is now valid for Fraction(), with the
exception of strings representing NaNs and infinities.
- Issue #5734: BufferedRWPair was poorly tested and had several glaring
bugs. Patch by Brian Quinlan.