Skip to content

Commit c91af34

Browse files
authored
Merge branch 'main' into increase-trashcan-overhead
2 parents ba9702c + 2dad1e0 commit c91af34

File tree

19 files changed

+312
-89
lines changed

19 files changed

+312
-89
lines changed

Doc/library/string.rst

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,14 +319,19 @@ non-empty format specification typically modifies the result.
319319
The general form of a *standard format specifier* is:
320320

321321
.. productionlist:: format-spec
322-
format_spec: [[`fill`]`align`][`sign`]["z"]["#"]["0"][`width`][`grouping_option`]["." `precision`][`type`]
322+
format_spec: [`options`][`width_and_precision`][`type`]
323+
options: [[`fill`]`align`][`sign`]["z"]["#"]["0"]
323324
fill: <any character>
324325
align: "<" | ">" | "=" | "^"
325326
sign: "+" | "-" | " "
327+
width_and_precision: [`width_with_grouping`][`precision_with_grouping`]
328+
width_with_grouping: [`width`][`grouping_option`]
329+
precision_with_grouping: "." [`precision`]`grouping_option`
326330
width: `~python-grammar:digit`+
327331
grouping_option: "_" | ","
328332
precision: `~python-grammar:digit`+
329-
type: "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g" | "G" | "n" | "o" | "s" | "x" | "X" | "%"
333+
type: "b" | "c" | "d" | "e" | "E" | "f" | "F" | "g"
334+
: | "G" | "n" | "o" | "s" | "x" | "X" | "%"
330335

331336
If a valid *align* value is specified, it can be preceded by a *fill*
332337
character that can be any character and defaults to a space if omitted.
@@ -458,6 +463,13 @@ indicates the maximum field size - in other words, how many characters will be
458463
used from the field content. The *precision* is not allowed for integer
459464
presentation types.
460465

466+
The ``'_'`` or ``','`` option after *precision* means the use of an underscore
467+
or a comma for a thousands separator of the fractional part for floating-point
468+
presentation types.
469+
470+
.. versionchanged:: 3.14
471+
Support thousands separators for the fractional part.
472+
461473
Finally, the *type* determines how the data should be presented.
462474

463475
The available string presentation types are:
@@ -704,10 +716,18 @@ Replacing ``%x`` and ``%o`` and converting the value to different bases::
704716
>>> "int: {0:d}; hex: {0:#x}; oct: {0:#o}; bin: {0:#b}".format(42)
705717
'int: 42; hex: 0x2a; oct: 0o52; bin: 0b101010'
706718

707-
Using the comma as a thousands separator::
719+
Using the comma or the underscore as a thousands separator::
708720

709721
>>> '{:,}'.format(1234567890)
710722
'1,234,567,890'
723+
>>> '{:_}'.format(1234567890)
724+
'1_234_567_890'
725+
>>> '{:_}'.format(123456789.123456789)
726+
'123_456_789.12345679'
727+
>>> '{:._}'.format(123456789.123456789)
728+
'123456789.123_456_79'
729+
>>> '{:_._}'.format(123456789.123456789)
730+
'123_456_789.123_456_79'
711731

712732
Expressing a percentage::
713733

Doc/whatsnew/3.14.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,11 @@ Other language changes
336336
making it a :term:`generic type`.
337337
(Contributed by Brian Schubert in :gh:`126012`.)
338338

339+
* Support underscore and comma as thousands separators in the fractional part
340+
for floating-point presentation types of the new-style string formatting
341+
(with :func:`format` or :ref:`f-strings`).
342+
(Contrubuted by Sergey B Kirpichev in :gh:`87790`.)
343+
339344
* ``\B`` in :mod:`regular expression <re>` now matches empty input string.
340345
Now it is always the opposite of ``\b``.
341346
(Contributed by Serhiy Storchaka in :gh:`124130`.)

Include/cpython/object.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
PyAPI_FUNC(void) _Py_NewReference(PyObject *op);
66
PyAPI_FUNC(void) _Py_NewReferenceNoTotal(PyObject *op);
77
PyAPI_FUNC(void) _Py_ResurrectReference(PyObject *op);
8+
PyAPI_FUNC(void) _Py_ForgetReference(PyObject *op);
89

910
#ifdef Py_REF_DEBUG
1011
/* These are useful as debugging aids when chasing down refleaks. */

Include/internal/pycore_object.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,9 @@ _PyObject_ResurrectStart(PyObject *op)
730730
#else
731731
Py_SET_REFCNT(op, 1);
732732
#endif
733+
#ifdef Py_TRACE_REFS
734+
_Py_ResurrectReference(op);
735+
#endif
733736
}
734737

735738
// Undoes an object resurrection by decrementing the refcount without calling
@@ -743,13 +746,22 @@ _PyObject_ResurrectEnd(PyObject *op)
743746
#endif
744747
#ifndef Py_GIL_DISABLED
745748
Py_SET_REFCNT(op, Py_REFCNT(op) - 1);
746-
return Py_REFCNT(op) != 0;
749+
if (Py_REFCNT(op) == 0) {
750+
# ifdef Py_TRACE_REFS
751+
_Py_ForgetReference(op);
752+
# endif
753+
return 0;
754+
}
755+
return 1;
747756
#else
748757
uint32_t local = _Py_atomic_load_uint32_relaxed(&op->ob_ref_local);
749758
Py_ssize_t shared = _Py_atomic_load_ssize_acquire(&op->ob_ref_shared);
750759
if (_Py_IsOwnedByCurrentThread(op) && local == 1 && shared == 0) {
751760
// Fast-path: object has a single refcount and is owned by this thread
752761
_Py_atomic_store_uint32_relaxed(&op->ob_ref_local, 0);
762+
# ifdef Py_TRACE_REFS
763+
_Py_ForgetReference(op);
764+
# endif
753765
return 0;
754766
}
755767
// Slow-path: object has a shared refcount or is not owned by this thread

Include/internal/pycore_unicodeobject.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,8 @@ extern Py_ssize_t _PyUnicode_InsertThousandsGrouping(
246246
Py_ssize_t min_width,
247247
const char *grouping,
248248
PyObject *thousands_sep,
249-
Py_UCS4 *maxchar);
249+
Py_UCS4 *maxchar,
250+
int forward);
250251

251252
/* --- Misc functions ----------------------------------------------------- */
252253

Include/pythonrun.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ PyAPI_DATA(int) (*PyOS_InputHook)(void);
2828
#if defined(_Py_ADDRESS_SANITIZER) || defined(_Py_THREAD_SANITIZER)
2929
# define PYOS_STACK_MARGIN 4096
3030
#elif defined(Py_DEBUG) && defined(WIN32)
31-
# define PYOS_STACK_MARGIN 3072
31+
# define PYOS_STACK_MARGIN 4096
3232
#elif defined(__wasi__)
3333
/* Web assembly has two stacks, so this isn't really a size */
3434
# define PYOS_STACK_MARGIN 500

Lib/test/libregrtest/tsan.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
'test_capi.test_mem',
77
'test_capi.test_pyatomic',
88
'test_code',
9+
'test_concurrent_futures',
910
'test_enum',
1011
'test_functools',
1112
'test_httpservers',

Lib/test/test_concurrent_futures/util.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ class ThreadPoolMixin(ExecutorMixin):
7474
executor_type = futures.ThreadPoolExecutor
7575

7676

77+
@support.skip_if_sanitizer("gh-129824: data races in InterpreterPool tests", thread=True)
7778
class InterpreterPoolMixin(ExecutorMixin):
7879
executor_type = futures.InterpreterPoolExecutor
7980

Lib/test/test_float.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,6 +754,28 @@ def test_format(self):
754754
self.assertEqual(format(INF, 'f'), 'inf')
755755
self.assertEqual(format(INF, 'F'), 'INF')
756756

757+
# thousands separators
758+
x = 123_456.123_456
759+
self.assertEqual(format(x, '_f'), '123_456.123456')
760+
self.assertEqual(format(x, ',f'), '123,456.123456')
761+
self.assertEqual(format(x, '._f'), '123456.123_456')
762+
self.assertEqual(format(x, '.,f'), '123456.123,456')
763+
self.assertEqual(format(x, '_._f'), '123_456.123_456')
764+
self.assertEqual(format(x, ',.,f'), '123,456.123,456')
765+
self.assertEqual(format(x, '.10_f'), '123456.123_456_000_0')
766+
self.assertEqual(format(x, '.10,f'), '123456.123,456,000,0')
767+
self.assertEqual(format(x, '>21._f'), ' 123456.123_456')
768+
self.assertEqual(format(x, '<21._f'), '123456.123_456 ')
769+
self.assertEqual(format(x, '+.11_e'), '+1.234_561_234_56e+05')
770+
self.assertEqual(format(x, '+.11,e'), '+1.234,561,234,56e+05')
771+
772+
self.assertRaises(ValueError, format, x, '._6f')
773+
self.assertRaises(ValueError, format, x, '.,_f')
774+
self.assertRaises(ValueError, format, x, '.6,_f')
775+
self.assertRaises(ValueError, format, x, '.6_,f')
776+
self.assertRaises(ValueError, format, x, '.6_n')
777+
self.assertRaises(ValueError, format, x, '.6,n')
778+
757779
@support.requires_IEEE_754
758780
def test_format_testfile(self):
759781
with open(format_testfile, encoding="utf-8") as testfile:

Lib/test/test_format.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -515,11 +515,15 @@ def test_with_a_commas_and_an_underscore_in_format_specifier(self):
515515
error_msg = re.escape("Cannot specify both ',' and '_'.")
516516
with self.assertRaisesRegex(ValueError, error_msg):
517517
'{:,_}'.format(1)
518+
with self.assertRaisesRegex(ValueError, error_msg):
519+
'{:.,_f}'.format(1.1)
518520

519521
def test_with_an_underscore_and_a_comma_in_format_specifier(self):
520522
error_msg = re.escape("Cannot specify both ',' and '_'.")
521523
with self.assertRaisesRegex(ValueError, error_msg):
522524
'{:_,}'.format(1)
525+
with self.assertRaisesRegex(ValueError, error_msg):
526+
'{:._,f}'.format(1.1)
523527

524528
def test_better_error_message_format(self):
525529
# https://bugs.python.org/issue20524

0 commit comments

Comments
 (0)