Skip to content

Commit e111e10

Browse files
authored
Merge branch 'main' into pairwise_v2
2 parents fdc7b01 + 67f6e08 commit e111e10

27 files changed

+459
-224
lines changed

Doc/library/decimal.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,23 @@ Decimal objects
598598

599599
.. versionadded:: 3.1
600600

601+
.. classmethod:: from_number(number)
602+
603+
Alternative constructor that only accepts instances of
604+
:class:`float`, :class:`int` or :class:`Decimal`, but not strings
605+
or tuples.
606+
607+
.. doctest::
608+
609+
>>> Decimal.from_number(314)
610+
Decimal('314')
611+
>>> Decimal.from_number(0.1)
612+
Decimal('0.1000000000000000055511151231257827021181583404541015625')
613+
>>> Decimal.from_number(Decimal('3.14'))
614+
Decimal('3.14')
615+
616+
.. versionadded:: 3.14
617+
601618
.. method:: fma(other, third, context=None)
602619

603620
Fused multiply-add. Return self*other+third with no rounding of the

Doc/library/fractions.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,16 @@ another rational number, or from a string.
166166
instance.
167167

168168

169+
.. classmethod:: from_number(number)
170+
171+
Alternative constructor which only accepts instances of
172+
:class:`numbers.Integral`, :class:`numbers.Rational`,
173+
:class:`float` or :class:`decimal.Decimal`, and objects with
174+
the :meth:`!as_integer_ratio` method, but not strings.
175+
176+
.. versionadded:: 3.14
177+
178+
169179
.. method:: limit_denominator(max_denominator=1000000)
170180

171181
Finds and returns the closest :class:`Fraction` to ``self`` that has

Doc/library/string.rst

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -509,9 +509,8 @@ The available presentation types for :class:`float` and
509509
| | significant digits. With no precision given, uses a |
510510
| | precision of ``6`` digits after the decimal point for |
511511
| | :class:`float`, and shows all coefficient digits |
512-
| | for :class:`~decimal.Decimal`. If no digits follow the |
513-
| | decimal point, the decimal point is also removed unless |
514-
| | the ``#`` option is used. |
512+
| | for :class:`~decimal.Decimal`. If ``p=0``, the decimal |
513+
| | point is omitted unless the ``#`` option is used. |
515514
+---------+----------------------------------------------------------+
516515
| ``'E'`` | Scientific notation. Same as ``'e'`` except it uses |
517516
| | an upper case 'E' as the separator character. |
@@ -522,9 +521,8 @@ The available presentation types for :class:`float` and
522521
| | precision given, uses a precision of ``6`` digits after |
523522
| | the decimal point for :class:`float`, and uses a |
524523
| | precision large enough to show all coefficient digits |
525-
| | for :class:`~decimal.Decimal`. If no digits follow the |
526-
| | decimal point, the decimal point is also removed unless |
527-
| | the ``#`` option is used. |
524+
| | for :class:`~decimal.Decimal`. If ``p=0``, the decimal |
525+
| | point is omitted unless the ``#`` option is used. |
528526
+---------+----------------------------------------------------------+
529527
| ``'F'`` | Fixed-point notation. Same as ``'f'``, but converts |
530528
| | ``nan`` to ``NAN`` and ``inf`` to ``INF``. |

Doc/whatsnew/3.14.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,12 @@ ctypes
239239
to help match a non-default ABI.
240240
(Contributed by Petr Viktorin in :gh:`97702`.)
241241

242+
decimal
243+
-------
244+
245+
* Add alternative :class:`~decimal.Decimal` constructor
246+
:meth:`Decimal.from_number() <decimal.Decimal.from_number>`.
247+
(Contributed by Serhiy Storchaka in :gh:`121798`.)
242248

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

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

267277
functools
268278
---------

Include/internal/pycore_lock.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ typedef struct {
160160

161161
PyAPI_FUNC(int) _PyRecursiveMutex_IsLockedByCurrentThread(_PyRecursiveMutex *m);
162162
PyAPI_FUNC(void) _PyRecursiveMutex_Lock(_PyRecursiveMutex *m);
163+
extern PyLockStatus _PyRecursiveMutex_LockTimed(_PyRecursiveMutex *m, PyTime_t timeout, _PyLockFlags flags);
163164
PyAPI_FUNC(void) _PyRecursiveMutex_Unlock(_PyRecursiveMutex *m);
164-
165+
extern int _PyRecursiveMutex_TryUnlock(_PyRecursiveMutex *m);
165166

166167
// A readers-writer (RW) lock. The lock supports multiple concurrent readers or
167168
// a single writer. The lock is write-preferring: if a writer is waiting while

Include/internal/pycore_stackref.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ PyStackRef_AsStrongReference(_PyStackRef stackref)
153153
return PyStackRef_FromPyObjectSteal(PyStackRef_AsPyObjectSteal(stackref));
154154
}
155155

156+
#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) PyStackRef_CLOSE(stackref)
157+
156158

157159
#else // Py_GIL_DISABLED
158160

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

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

182+
#define PyStackRef_CLOSE_SPECIALIZED(stackref, dealloc) _Py_DECREF_SPECIALIZED(PyStackRef_AsPyObjectBorrow(stackref), dealloc)
180183

181184
#endif // Py_GIL_DISABLED
182185

Lib/_pydecimal.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,21 @@ def __new__(cls, value="0", context=None):
582582

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

585+
@classmethod
586+
def from_number(cls, number):
587+
"""Converts a real number to a decimal number, exactly.
588+
589+
>>> Decimal.from_number(314) # int
590+
Decimal('314')
591+
>>> Decimal.from_number(0.1) # float
592+
Decimal('0.1000000000000000055511151231257827021181583404541015625')
593+
>>> Decimal.from_number(Decimal('3.14')) # another decimal instance
594+
Decimal('3.14')
595+
"""
596+
if isinstance(number, (int, Decimal, float)):
597+
return cls(number)
598+
raise TypeError("Cannot convert %r to Decimal" % number)
599+
585600
@classmethod
586601
def from_float(cls, f):
587602
"""Converts a float to a decimal number, exactly.

Lib/argparse.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,8 +547,7 @@ def _metavar_formatter(self, action, default_metavar):
547547
if action.metavar is not None:
548548
result = action.metavar
549549
elif action.choices is not None:
550-
choice_strs = [str(choice) for choice in action.choices]
551-
result = '{%s}' % ','.join(choice_strs)
550+
result = '{%s}' % ','.join(map(str, action.choices))
552551
else:
553552
result = default_metavar
554553

@@ -599,8 +598,7 @@ def _expand_help(self, action):
599598
elif hasattr(value, '__name__'):
600599
params[name] = value.__name__
601600
if params.get('choices') is not None:
602-
choices_str = ', '.join([str(c) for c in params['choices']])
603-
params['choices'] = choices_str
601+
params['choices'] = ', '.join(map(str, params['choices']))
604602
return help_string % params
605603

606604
def _iter_indented_subactions(self, action):
@@ -717,7 +715,7 @@ def _get_action_name(argument):
717715
elif argument.dest not in (None, SUPPRESS):
718716
return argument.dest
719717
elif argument.choices:
720-
return '{' + ','.join(argument.choices) + '}'
718+
return '{%s}' % ','.join(map(str, argument.choices))
721719
else:
722720
return None
723721

@@ -2607,8 +2605,8 @@ def _check_value(self, action, value):
26072605
if isinstance(choices, str):
26082606
choices = iter(choices)
26092607
if value not in choices:
2610-
args = {'value': value,
2611-
'choices': ', '.join(map(repr, action.choices))}
2608+
args = {'value': str(value),
2609+
'choices': ', '.join(map(str, action.choices))}
26122610
msg = _('invalid choice: %(value)r (choose from %(choices)s)')
26132611
raise ArgumentError(action, msg % args)
26142612

Lib/fractions.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ def __new__(cls, numerator=0, denominator=None):
279279
numerator = -numerator
280280

281281
else:
282-
raise TypeError("argument should be a string or a number")
282+
raise TypeError("argument should be a string or a Rational "
283+
"instance or have the as_integer_ratio() method")
283284

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

309+
@classmethod
310+
def from_number(cls, number):
311+
"""Converts a finite real number to a rational number, exactly.
312+
313+
Beware that Fraction.from_number(0.3) != Fraction(3, 10).
314+
315+
"""
316+
if type(number) is int:
317+
return cls._from_coprime_ints(number, 1)
318+
319+
elif isinstance(number, numbers.Rational):
320+
return cls._from_coprime_ints(number.numerator, number.denominator)
321+
322+
elif (isinstance(number, float) or
323+
(not isinstance(number, type) and
324+
hasattr(number, 'as_integer_ratio'))):
325+
return cls._from_coprime_ints(*number.as_integer_ratio())
326+
327+
else:
328+
raise TypeError("argument should be a Rational instance or "
329+
"have the as_integer_ratio() method")
330+
308331
@classmethod
309332
def from_float(cls, f):
310333
"""Converts a finite float to a rational number, exactly.

Lib/test/test_argparse.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import argparse
1717
import warnings
1818

19+
from enum import StrEnum
1920
from test.support import captured_stderr
2021
from test.support import import_helper
2122
from test.support import os_helper
@@ -985,6 +986,34 @@ class TestDisallowLongAbbreviationAllowsShortGroupingPrefix(ParserTestCase):
985986
]
986987

987988

989+
class TestStrEnumChoices(TestCase):
990+
class Color(StrEnum):
991+
RED = "red"
992+
GREEN = "green"
993+
BLUE = "blue"
994+
995+
def test_parse_enum_value(self):
996+
parser = argparse.ArgumentParser()
997+
parser.add_argument('--color', choices=self.Color)
998+
args = parser.parse_args(['--color', 'red'])
999+
self.assertEqual(args.color, self.Color.RED)
1000+
1001+
def test_help_message_contains_enum_choices(self):
1002+
parser = argparse.ArgumentParser()
1003+
parser.add_argument('--color', choices=self.Color, help='Choose a color')
1004+
self.assertIn('[--color {red,green,blue}]', parser.format_usage())
1005+
self.assertIn(' --color {red,green,blue}', parser.format_help())
1006+
1007+
def test_invalid_enum_value_raises_error(self):
1008+
parser = argparse.ArgumentParser(exit_on_error=False)
1009+
parser.add_argument('--color', choices=self.Color)
1010+
self.assertRaisesRegex(
1011+
argparse.ArgumentError,
1012+
r"invalid choice: 'yellow' \(choose from red, green, blue\)",
1013+
parser.parse_args,
1014+
['--color', 'yellow'],
1015+
)
1016+
9881017
# ================
9891018
# Positional tests
9901019
# ================
@@ -2485,7 +2514,7 @@ def test_wrong_argument_subparsers_no_destination_error(self):
24852514
parser.parse_args(('baz',))
24862515
self.assertRegex(
24872516
excinfo.exception.stderr,
2488-
r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from 'foo', 'bar'\)\n$"
2517+
r"error: argument {foo,bar}: invalid choice: 'baz' \(choose from foo, bar\)\n$"
24892518
)
24902519

24912520
def test_optional_subparsers(self):

0 commit comments

Comments
 (0)