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 :exc:`ZeroDivisionError`. The second version requires that
*other_fraction* is an instance of :class:`numbers.Rational` and *other_fraction* is an instance of :class:`numbers.Rational` and
returns an :class:`Fraction` instance with the same value. The returns an :class:`Fraction` instance with the same value. The
last version of the constructor expects a string last version of the constructor expects a string instance. The
instance in one of two possible forms. The first form is:: usual form for this string is::
[sign] numerator ['/' denominator] [sign] numerator ['/' denominator]
where the optional ``sign`` may be either '+' or '-' and where the optional ``sign`` may be either '+' or '-' and
``numerator`` and ``denominator`` (if present) are strings of ``numerator`` and ``denominator`` (if present) are strings of
decimal digits. The second permitted form is that of a number decimal digits. In addition, any string that represents a finite
containing a decimal point:: value and is accepted by the :class:`float` constructor is also
accepted by the :class:`Fraction` constructor. In either form the
[sign] integer '.' [fraction] | [sign] '.' fraction input string may also have leading and/or trailing whitespace.
Here are some examples::
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::
>>> from fractions import Fraction >>> from fractions import Fraction
>>> Fraction(16, -10) >>> Fraction(16, -10)
@ -57,6 +54,8 @@ another rational number, or from a string.
Fraction(1414213, 1000000) Fraction(1414213, 1000000)
>>> Fraction('-.125') >>> Fraction('-.125')
Fraction(-1, 8) Fraction(-1, 8)
>>> Fraction('7e-6')
Fraction(7, 1000000)
The :class:`Fraction` class inherits from the abstract base class 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 (?P<sign>[-+]?) # an optional sign, then
(?=\d|\.\d) # lookahead for digit or .digit (?=\d|\.\d) # lookahead for digit or .digit
(?P<num>\d*) # numerator (possibly empty) (?P<num>\d*) # numerator (possibly empty)
(?: # followed by an optional (?: # followed by
/(?P<denom>\d+) # / and denominator (?:/(?P<denom>\d+))? # an optional denominator
| # or | # 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 \s*\Z # and optional whitespace to finish
""", re.VERBOSE) """, re.VERBOSE | re.IGNORECASE)
class Fraction(numbers.Rational): class Fraction(numbers.Rational):
@ -65,22 +66,28 @@ class Fraction(numbers.Rational):
if not isinstance(numerator, int) and denominator == 1: if not isinstance(numerator, int) and denominator == 1:
if isinstance(numerator, str): if isinstance(numerator, str):
# Handle construction from strings. # Handle construction from strings.
input = numerator m = _RATIONAL_FORMAT.match(numerator)
m = _RATIONAL_FORMAT.match(input)
if m is None: if m is None:
raise ValueError('Invalid literal for Fraction: %r' % input) raise ValueError('Invalid literal for Fraction: %r' %
numerator = m.group('num') numerator)
decimal = m.group('decimal') numerator = int(m.group('num') or '0')
if decimal: denom = m.group('denom')
# The literal is a decimal number. if denom:
numerator = int(numerator + decimal) denominator = int(denom)
denominator = 10**len(decimal)
else: else:
# The literal is an integer or fraction. denominator = 1
numerator = int(numerator) decimal = m.group('decimal')
# Default denominator to 1. if decimal:
denominator = int(m.group('denom') or 1) 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') == '-': if m.group('sign') == '-':
numerator = -numerator numerator = -numerator

View file

@ -78,6 +78,11 @@ class FractionTest(unittest.TestCase):
self.assertEquals((-16, 5), _components(F(" -3.2 "))) self.assertEquals((-16, 5), _components(F(" -3.2 ")))
self.assertEquals((-3, 1), _components(F(" -3. "))) self.assertEquals((-3, 1), _components(F(" -3. ")))
self.assertEquals((3, 5), _components(F(" .6 "))) 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( self.assertRaisesMessage(
ZeroDivisionError, "Fraction(3, 0)", ZeroDivisionError, "Fraction(3, 0)",
@ -85,6 +90,9 @@ class FractionTest(unittest.TestCase):
self.assertRaisesMessage( self.assertRaisesMessage(
ValueError, "Invalid literal for Fraction: '3/'", ValueError, "Invalid literal for Fraction: '3/'",
F, "3/") F, "3/")
self.assertRaisesMessage(
ValueError, "Invalid literal for Fraction: '/2'",
F, "/2")
self.assertRaisesMessage( self.assertRaisesMessage(
ValueError, "Invalid literal for Fraction: '3 /2'", ValueError, "Invalid literal for Fraction: '3 /2'",
F, "3 /2") F, "3 /2")
@ -100,10 +108,6 @@ class FractionTest(unittest.TestCase):
# Avoid treating '.' as a regex special character. # Avoid treating '.' as a regex special character.
ValueError, "Invalid literal for Fraction: '3a2'", ValueError, "Invalid literal for Fraction: '3a2'",
F, "3a2") F, "3a2")
self.assertRaisesMessage(
# Only parse ordinary decimals, not scientific form.
ValueError, "Invalid literal for Fraction: '3.2e4'",
F, "3.2e4")
self.assertRaisesMessage( self.assertRaisesMessage(
# Don't accept combinations of decimals and rationals. # Don't accept combinations of decimals and rationals.
ValueError, "Invalid literal for Fraction: '3/7.2'", ValueError, "Invalid literal for Fraction: '3/7.2'",

View file

@ -75,6 +75,10 @@ Core and Builtins
Library 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 - Issue #5734: BufferedRWPair was poorly tested and had several glaring
bugs. Patch by Brian Quinlan. bugs. Patch by Brian Quinlan.