mirror of
https://github.com/python/cpython.git
synced 2025-09-26 18:29:57 +00:00
bpo-44258: support PEP 515 for Fraction's initialization from string (GH-26422)
* bpo-44258: support PEP 515 for Fraction's initialization from string * regexps's version * A different regexps version, which doesn't suffer from catastrophic backtracking * revert denom -> den * strip "_" from the decimal str, add few tests * drop redundant tests * Add versionchanged & whatsnew entry * Amend Fraction constructor docs * Change .. versionchanged:...
This commit is contained in:
parent
afb2eed72b
commit
89e50ab36f
5 changed files with 85 additions and 11 deletions
|
@ -42,7 +42,8 @@ another rational number, or from a string.
|
||||||
|
|
||||||
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. In addition, any string that represents a finite
|
decimal digits (underscores may be used to delimit digits as with
|
||||||
|
integral literals in code). In addition, any string that represents a finite
|
||||||
value and is accepted by the :class:`float` constructor is also
|
value and is accepted by the :class:`float` constructor is also
|
||||||
accepted by the :class:`Fraction` constructor. In either form the
|
accepted by the :class:`Fraction` constructor. In either form the
|
||||||
input string may also have leading and/or trailing whitespace.
|
input string may also have leading and/or trailing whitespace.
|
||||||
|
@ -89,6 +90,10 @@ another rational number, or from a string.
|
||||||
and *denominator*. :func:`math.gcd` always return a :class:`int` type.
|
and *denominator*. :func:`math.gcd` always return a :class:`int` type.
|
||||||
Previously, the GCD type depended on *numerator* and *denominator*.
|
Previously, the GCD type depended on *numerator* and *denominator*.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
Underscores are now permitted when creating a :class:`Fraction` instance
|
||||||
|
from a string, following :PEP:`515` rules.
|
||||||
|
|
||||||
.. attribute:: numerator
|
.. attribute:: numerator
|
||||||
|
|
||||||
Numerator of the Fraction in lowest term.
|
Numerator of the Fraction in lowest term.
|
||||||
|
|
|
@ -86,6 +86,11 @@ New Modules
|
||||||
Improved Modules
|
Improved Modules
|
||||||
================
|
================
|
||||||
|
|
||||||
|
fractions
|
||||||
|
---------
|
||||||
|
|
||||||
|
Support :PEP:`515`-style initialization of :class:`~fractions.Fraction` from
|
||||||
|
string. (Contributed by Sergey B Kirpichev in :issue:`44258`.)
|
||||||
|
|
||||||
Optimizations
|
Optimizations
|
||||||
=============
|
=============
|
||||||
|
|
|
@ -21,17 +21,17 @@ _PyHASH_MODULUS = sys.hash_info.modulus
|
||||||
_PyHASH_INF = sys.hash_info.inf
|
_PyHASH_INF = sys.hash_info.inf
|
||||||
|
|
||||||
_RATIONAL_FORMAT = re.compile(r"""
|
_RATIONAL_FORMAT = re.compile(r"""
|
||||||
\A\s* # optional whitespace at the start, then
|
\A\s* # optional whitespace at the start,
|
||||||
(?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*|\d+(_\d+)*) # numerator (possibly empty)
|
||||||
(?: # followed by
|
(?: # followed by
|
||||||
(?:/(?P<denom>\d+))? # an optional denominator
|
(?:/(?P<denom>\d+(_\d+)*))? # an optional denominator
|
||||||
| # or
|
| # or
|
||||||
(?:\.(?P<decimal>\d*))? # an optional fractional part
|
(?:\.(?P<decimal>d*|\d+(_\d+)*))? # an optional fractional part
|
||||||
(?:E(?P<exp>[-+]?\d+))? # and optional exponent
|
(?:E(?P<exp>[-+]?\d+(_\d+)*))? # and optional exponent
|
||||||
)
|
)
|
||||||
\s*\Z # and optional whitespace to finish
|
\s*\Z # and optional whitespace to finish
|
||||||
""", re.VERBOSE | re.IGNORECASE)
|
""", re.VERBOSE | re.IGNORECASE)
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,6 +122,7 @@ class Fraction(numbers.Rational):
|
||||||
denominator = 1
|
denominator = 1
|
||||||
decimal = m.group('decimal')
|
decimal = m.group('decimal')
|
||||||
if decimal:
|
if decimal:
|
||||||
|
decimal = decimal.replace('_', '')
|
||||||
scale = 10**len(decimal)
|
scale = 10**len(decimal)
|
||||||
numerator = numerator * scale + int(decimal)
|
numerator = numerator * scale + int(decimal)
|
||||||
denominator *= scale
|
denominator *= scale
|
||||||
|
|
|
@ -173,6 +173,12 @@ class FractionTest(unittest.TestCase):
|
||||||
self.assertEqual((-12300, 1), _components(F("-1.23e4")))
|
self.assertEqual((-12300, 1), _components(F("-1.23e4")))
|
||||||
self.assertEqual((0, 1), _components(F(" .0e+0\t")))
|
self.assertEqual((0, 1), _components(F(" .0e+0\t")))
|
||||||
self.assertEqual((0, 1), _components(F("-0.000e0")))
|
self.assertEqual((0, 1), _components(F("-0.000e0")))
|
||||||
|
self.assertEqual((123, 1), _components(F("1_2_3")))
|
||||||
|
self.assertEqual((41, 107), _components(F("1_2_3/3_2_1")))
|
||||||
|
self.assertEqual((6283, 2000), _components(F("3.14_15")))
|
||||||
|
self.assertEqual((6283, 2*10**13), _components(F("3.14_15e-1_0")))
|
||||||
|
self.assertEqual((101, 100), _components(F("1.01")))
|
||||||
|
self.assertEqual((101, 100), _components(F("1.0_1")))
|
||||||
|
|
||||||
self.assertRaisesMessage(
|
self.assertRaisesMessage(
|
||||||
ZeroDivisionError, "Fraction(3, 0)",
|
ZeroDivisionError, "Fraction(3, 0)",
|
||||||
|
@ -210,6 +216,62 @@ class FractionTest(unittest.TestCase):
|
||||||
# Allow 3. and .3, but not .
|
# Allow 3. and .3, but not .
|
||||||
ValueError, "Invalid literal for Fraction: '.'",
|
ValueError, "Invalid literal for Fraction: '.'",
|
||||||
F, ".")
|
F, ".")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '_'",
|
||||||
|
F, "_")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '_1'",
|
||||||
|
F, "_1")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1__2'",
|
||||||
|
F, "1__2")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '/_'",
|
||||||
|
F, "/_")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1_/'",
|
||||||
|
F, "1_/")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '_1/'",
|
||||||
|
F, "_1/")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1__2/'",
|
||||||
|
F, "1__2/")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1/_'",
|
||||||
|
F, "1/_")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1/_1'",
|
||||||
|
F, "1/_1")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1/1__2'",
|
||||||
|
F, "1/1__2")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1._111'",
|
||||||
|
F, "1._111")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1.1__1'",
|
||||||
|
F, "1.1__1")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1.1e+_1'",
|
||||||
|
F, "1.1e+_1")
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1.1e+1__1'",
|
||||||
|
F, "1.1e+1__1")
|
||||||
|
# Test catastrophic backtracking.
|
||||||
|
val = "9"*50 + "_"
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '" + val + "'",
|
||||||
|
F, val)
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1/" + val + "'",
|
||||||
|
F, "1/" + val)
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1." + val + "'",
|
||||||
|
F, "1." + val)
|
||||||
|
self.assertRaisesMessage(
|
||||||
|
ValueError, "Invalid literal for Fraction: '1.1+e" + val + "'",
|
||||||
|
F, "1.1+e" + val)
|
||||||
|
|
||||||
def testImmutable(self):
|
def testImmutable(self):
|
||||||
r = F(7, 3)
|
r = F(7, 3)
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Support PEP 515 for Fraction's initialization from string.
|
Loading…
Add table
Add a link
Reference in a new issue