Skip to content

Commit

Permalink
Merge branch 'main' into pairwise_v2
Browse files Browse the repository at this point in the history
  • Loading branch information
eendebakpt authored Oct 14, 2024
2 parents fdc7b01 + 67f6e08 commit e111e10
Show file tree
Hide file tree
Showing 27 changed files with 459 additions and 224 deletions.
17 changes: 17 additions & 0 deletions Doc/library/decimal.rst
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,23 @@ Decimal objects

.. versionadded:: 3.1

.. classmethod:: from_number(number)

Alternative constructor that only accepts instances of
:class:`float`, :class:`int` or :class:`Decimal`, but not strings
or tuples.

.. doctest::

>>> Decimal.from_number(314)
Decimal('314')
>>> Decimal.from_number(0.1)
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal.from_number(Decimal('3.14'))
Decimal('3.14')

.. versionadded:: 3.14

.. method:: fma(other, third, context=None)

Fused multiply-add. Return self*other+third with no rounding of the
Expand Down
10 changes: 10 additions & 0 deletions Doc/library/fractions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,16 @@ another rational number, or from a string.
instance.


.. classmethod:: from_number(number)

Alternative constructor which only accepts instances of
:class:`numbers.Integral`, :class:`numbers.Rational`,
:class:`float` or :class:`decimal.Decimal`, and objects with
the :meth:`!as_integer_ratio` method, but not strings.

.. versionadded:: 3.14


.. method:: limit_denominator(max_denominator=1000000)

Finds and returns the closest :class:`Fraction` to ``self`` that has
Expand Down
10 changes: 4 additions & 6 deletions Doc/library/string.rst
Original file line number Diff line number Diff line change
Expand Up @@ -509,9 +509,8 @@ The available presentation types for :class:`float` and
| | significant digits. With no precision given, uses a |
| | precision of ``6`` digits after the decimal point for |
| | :class:`float`, and shows all coefficient digits |
| | for :class:`~decimal.Decimal`. If no digits follow the |
| | decimal point, the decimal point is also removed unless |
| | the ``#`` option is used. |
| | for :class:`~decimal.Decimal`. If ``p=0``, the decimal |
| | point is omitted unless the ``#`` option is used. |
+---------+----------------------------------------------------------+
| ``'E'`` | Scientific notation. Same as ``'e'`` except it uses |
| | an upper case 'E' as the separator character. |
Expand All @@ -522,9 +521,8 @@ The available presentation types for :class:`float` and
| | precision given, uses a precision of ``6`` digits after |
| | the decimal point for :class:`float`, and uses a |
| | precision large enough to show all coefficient digits |
| | for :class:`~decimal.Decimal`. If no digits follow the |
| | decimal point, the decimal point is also removed unless |
| | the ``#`` option is used. |
| | for :class:`~decimal.Decimal`. If ``p=0``, the decimal |
| | point is omitted unless the ``#`` option is used. |
+---------+----------------------------------------------------------+
| ``'F'`` | Fixed-point notation. Same as ``'f'``, but converts |
| | ``nan`` to ``NAN`` and ``inf`` to ``INF``. |
Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,12 @@ ctypes
to help match a non-default ABI.
(Contributed by Petr Viktorin in :gh:`97702`.)

decimal
-------

* Add alternative :class:`~decimal.Decimal` constructor
:meth:`Decimal.from_number() <decimal.Decimal.from_number>`.
(Contributed by Serhiy Storchaka in :gh:`121798`.)

dis
---
Expand All @@ -263,6 +269,10 @@ fractions
:meth:`!as_integer_ratio` method to a :class:`~fractions.Fraction`.
(Contributed by Serhiy Storchaka in :gh:`82017`.)

* Add alternative :class:`~fractions.Fraction` constructor
:meth:`Fraction.from_number() <fractions.Fraction.from_number>`.
(Contributed by Serhiy Storchaka in :gh:`121797`.)


functools
---------
Expand Down
3 changes: 2 additions & 1 deletion Include/internal/pycore_lock.h
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ typedef struct {

PyAPI_FUNC(int) _PyRecursiveMutex_IsLockedByCurrentThread(_PyRecursiveMutex *m);
PyAPI_FUNC(void) _PyRecursiveMutex_Lock(_PyRecursiveMutex *m);
extern PyLockStatus _PyRecursiveMutex_LockTimed(_PyRecursiveMutex *m, PyTime_t timeout, _PyLockFlags flags);
PyAPI_FUNC(void) _PyRecursiveMutex_Unlock(_PyRecursiveMutex *m);

extern int _PyRecursiveMutex_TryUnlock(_PyRecursiveMutex *m);

// A readers-writer (RW) lock. The lock supports multiple concurrent readers or
// a single writer. The lock is write-preferring: if a writer is waiting while
Expand Down
3 changes: 3 additions & 0 deletions Include/internal/pycore_stackref.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ PyStackRef_AsStrongReference(_PyStackRef stackref)
return PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(stackref));
}

#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) PyStackRef_CLOSE(stackref)


#else // Py_GIL_DISABLED

Expand All @@ -177,6 +179,7 @@ static const _PyStackRef PyStackRef_NULL = { .bits = 0 };

#define PyStackRef_DUP(stackref) PyStackRef_FromPyObjectSteal(Py_NewRef(PyStackRef_AsPyObjectBorrow(stackref)))

#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) _Py_DECREF_SPECIALIZED(PyStackRef_AsPyObjectBorrow(stackref), dealloc)

#endif // Py_GIL_DISABLED

Expand Down
15 changes: 15 additions & 0 deletions Lib/_pydecimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,21 @@ def __new__(cls, value="0", context=None):

raise TypeError("Cannot convert %r to Decimal" % value)

@classmethod
def from_number(cls, number):
"""Converts a real number to a decimal number, exactly.
>>> Decimal.from_number(314) # int
Decimal('314')
>>> Decimal.from_number(0.1) # float
Decimal('0.1000000000000000055511151231257827021181583404541015625')
>>> Decimal.from_number(Decimal('3.14')) # another decimal instance
Decimal('3.14')
"""
if isinstance(number, (int, Decimal, float)):
return cls(number)
raise TypeError("Cannot convert %r to Decimal" % number)

@classmethod
def from_float(cls, f):
"""Converts a float to a decimal number, exactly.
Expand Down
12 changes: 5 additions & 7 deletions Lib/argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,8 +547,7 @@ def _metavar_formatter(self, action, default_metavar):
if action.metavar is not None:
result = action.metavar
elif action.choices is not None:
choice_strs = [str(choice) for choice in action.choices]
result = '{%s}' % ','.join(choice_strs)
result = '{%s}' % ','.join(map(str, action.choices))
else:
result = default_metavar

Expand Down Expand Up @@ -599,8 +598,7 @@ def _expand_help(self, action):
elif hasattr(value, '__name__'):
params[name] = value.__name__
if params.get('choices') is not None:
choices_str = ', '.join([str(c) for c in params['choices']])
params['choices'] = choices_str
params['choices'] = ', '.join(map(str, params['choices']))
return help_string % params

def _iter_indented_subactions(self, action):
Expand Down Expand Up @@ -717,7 +715,7 @@ def _get_action_name(argument):
elif argument.dest not in (None, SUPPRESS):
return argument.dest
elif argument.choices:
return '{' + ','.join(argument.choices) + '}'
return '{%s}' % ','.join(map(str, argument.choices))
else:
return None

Expand Down Expand Up @@ -2607,8 +2605,8 @@ def _check_value(self, action, value):
if isinstance(choices, str):
choices = iter(choices)
if value not in choices:
args = {'value': value,
'choices': ', '.join(map(repr, action.choices))}
args = {'value': str(value),
'choices': ', '.join(map(str, action.choices))}
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
raise ArgumentError(action, msg % args)

Expand Down
25 changes: 24 additions & 1 deletion Lib/fractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,8 @@ def __new__(cls, numerator=0, denominator=None):
numerator = -numerator

else:
raise TypeError("argument should be a string or a number")
raise TypeError("argument should be a string or a Rational "
"instance or have the as_integer_ratio() method")

elif type(numerator) is int is type(denominator):
pass # *very* normal case
Expand All @@ -305,6 +306,28 @@ def __new__(cls, numerator=0, denominator=None):
self._denominator = denominator
return self

@classmethod
def from_number(cls, number):
"""Converts a finite real number to a rational number, exactly.
Beware that Fraction.from_number(0.3) != Fraction(3, 10).
"""
if type(number) is int:
return cls._from_coprime_ints(number, 1)

elif isinstance(number, numbers.Rational):
return cls._from_coprime_ints(number.numerator, number.denominator)

elif (isinstance(number, float) or
(not isinstance(number, type) and
hasattr(number, 'as_integer_ratio'))):
return cls._from_coprime_ints(*number.as_integer_ratio())

else:
raise TypeError("argument should be a Rational instance or "
"have the as_integer_ratio() method")

@classmethod
def from_float(cls, f):
"""Converts a finite float to a rational number, exactly.
Expand Down
31 changes: 30 additions & 1 deletion Lib/test/test_argparse.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import argparse
import warnings

from enum import StrEnum
from test.support import captured_stderr
from test.support import import_helper
from test.support import os_helper
Expand Down Expand Up @@ -985,6 +986,34 @@ class TestDisallowLongAbbreviationAllowsShortGroupingPrefix(ParserTestCase):
]


class TestStrEnumChoices(TestCase):
class Color(StrEnum):
RED = "red"
GREEN = "green"
BLUE = "blue"

def test_parse_enum_value(self):
parser = argparse.ArgumentParser()
parser.add_argument('--color', choices=self.Color)
args = parser.parse_args(['--color', 'red'])
self.assertEqual(args.color, self.Color.RED)

def test_help_message_contains_enum_choices(self):
parser = argparse.ArgumentParser()
parser.add_argument('--color', choices=self.Color, help='Choose a color')
self.assertIn('[--color {red,green,blue}]', parser.format_usage())
self.assertIn(' --color {red,green,blue}', parser.format_help())

def test_invalid_enum_value_raises_error(self):
parser = argparse.ArgumentParser(exit_on_error=False)
parser.add_argument('--color', choices=self.Color)
self.assertRaisesRegex(
argparse.ArgumentError,
r"invalid choice: 'yellow' \(choose from red, green, blue\)",
parser.parse_args,
['--color', 'yellow'],
)

# ================
# Positional tests
# ================
Expand Down Expand Up @@ -2485,7 +2514,7 @@ def test_wrong_argument_subparsers_no_destination_error(self):
parser.parse_args(('baz',))
self.assertRegex(
excinfo.exception.stderr,
r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$"
r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from foo, bar\)\n$"
)

def test_optional_subparsers(self):
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,29 @@ def test_explicit_context_create_from_float(self):
x = random.expovariate(0.01) * (random.random() * 2.0 - 1.0)
self.assertEqual(x, float(nc.create_decimal(x))) # roundtrip

def test_from_number(self, cls=None):
Decimal = self.decimal.Decimal
if cls is None:
cls = Decimal

def check(arg, expected):
d = cls.from_number(arg)
self.assertIs(type(d), cls)
self.assertEqual(d, expected)

check(314, Decimal(314))
check(3.14, Decimal.from_float(3.14))
check(Decimal('3.14'), Decimal('3.14'))
self.assertRaises(TypeError, cls.from_number, 3+4j)
self.assertRaises(TypeError, cls.from_number, '314')
self.assertRaises(TypeError, cls.from_number, (0, (3, 1, 4), 0))
self.assertRaises(TypeError, cls.from_number, object())

def test_from_number_subclass(self, cls=None):
class DecimalSubclass(self.decimal.Decimal):
pass
self.test_from_number(DecimalSubclass)

def test_unicode_digits(self):
Decimal = self.decimal.Decimal

Expand Down
49 changes: 42 additions & 7 deletions Lib/test/test_fractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,13 @@ def __repr__(self):
class RectComplex(Rect, complex):
pass

class Ratio:
def __init__(self, ratio):
self._ratio = ratio
def as_integer_ratio(self):
return self._ratio


class FractionTest(unittest.TestCase):

def assertTypedEquals(self, expected, actual):
Expand Down Expand Up @@ -355,14 +362,9 @@ def testInitFromDecimal(self):
self.assertRaises(OverflowError, F, Decimal('-inf'))

def testInitFromIntegerRatio(self):
class Ratio:
def __init__(self, ratio):
self._ratio = ratio
def as_integer_ratio(self):
return self._ratio

self.assertEqual((7, 3), _components(F(Ratio((7, 3)))))
errmsg = "argument should be a string or a number"
errmsg = (r"argument should be a string or a Rational instance or "
r"have the as_integer_ratio\(\) method")
# the type also has an "as_integer_ratio" attribute.
self.assertRaisesRegex(TypeError, errmsg, F, Ratio)
# bad ratio
Expand All @@ -388,6 +390,8 @@ class B(metaclass=M):
pass
self.assertRaisesRegex(TypeError, errmsg, F, B)
self.assertRaisesRegex(TypeError, errmsg, F, B())
self.assertRaises(TypeError, F.from_number, B)
self.assertRaises(TypeError, F.from_number, B())

def testFromString(self):
self.assertEqual((5, 1), _components(F("5")))
Expand Down Expand Up @@ -594,6 +598,37 @@ def testFromDecimal(self):
ValueError, "cannot convert NaN to integer ratio",
F.from_decimal, Decimal("snan"))

def testFromNumber(self, cls=F):
def check(arg, numerator, denominator):
f = cls.from_number(arg)
self.assertIs(type(f), cls)
self.assertEqual(f.numerator, numerator)
self.assertEqual(f.denominator, denominator)

check(10, 10, 1)
check(2.5, 5, 2)
check(Decimal('2.5'), 5, 2)
check(F(22, 7), 22, 7)
check(DummyFraction(22, 7), 22, 7)
check(Rat(22, 7), 22, 7)
check(Ratio((22, 7)), 22, 7)
self.assertRaises(TypeError, cls.from_number, 3+4j)
self.assertRaises(TypeError, cls.from_number, '5/2')
self.assertRaises(TypeError, cls.from_number, [])
self.assertRaises(OverflowError, cls.from_number, float('inf'))
self.assertRaises(OverflowError, cls.from_number, Decimal('inf'))

# as_integer_ratio not defined in a class
class A:
pass
a = A()
a.as_integer_ratio = lambda: (9, 5)
check(a, 9, 5)

def testFromNumber_subclass(self):
self.testFromNumber(DummyFraction)


def test_is_integer(self):
self.assertTrue(F(1, 1).is_integer())
self.assertTrue(F(-1, 1).is_integer())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Always use :func:`str` to print ``choices`` in :mod:`argparse`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add alternative :class:`~decimal.Decimal` constructor
:meth:`Decimal.from_number() <decimal.Decimal.from_number>`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add alternative :class:`~fractions.Fraction` constructor
:meth:`Fraction.from_number() <fractions.Fraction.from_number>`.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix data race when creating :class:`zoneinfo.ZoneInfo` objects in the free
threading build.
Loading

0 comments on commit e111e10

Please sign in to comment.