Skip to content

Commit 8cc270a

Browse files
authored
Merge branch 'main' into main
2 parents a5c0e64 + 847f83e commit 8cc270a

File tree

18 files changed

+493
-27
lines changed

18 files changed

+493
-27
lines changed

Doc/c-api/typeobj.rst

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3057,6 +3057,24 @@ Buffer Object Structures
30573057

30583058
(5) Return ``0``.
30593059

3060+
**Thread safety:**
3061+
3062+
In the :term:`free-threaded build`, implementations must ensure:
3063+
3064+
* The export counter increment in step (3) is atomic.
3065+
3066+
* The underlying buffer data remains valid and at a stable memory
3067+
location for the lifetime of all exports.
3068+
3069+
* For objects that support resizing or reallocation (such as
3070+
:class:`bytearray`), the export counter is checked atomically before
3071+
such operations, and :exc:`BufferError` is raised if exports exist.
3072+
3073+
* The function is safe to call concurrently from multiple threads.
3074+
3075+
See also :ref:`thread-safety-memoryview` for the Python-level
3076+
thread safety guarantees of :class:`memoryview` objects.
3077+
30603078
If *exporter* is part of a chain or tree of buffer providers, two main
30613079
schemes can be used:
30623080

@@ -3102,6 +3120,16 @@ Buffer Object Structures
31023120

31033121
(2) If the counter is ``0``, free all memory associated with *view*.
31043122

3123+
**Thread safety:**
3124+
3125+
In the :term:`free-threaded build`:
3126+
3127+
* The export counter decrement in step (1) must be atomic.
3128+
3129+
* Resource cleanup when the counter reaches zero must be done atomically,
3130+
as the final release may race with concurrent releases from other
3131+
threads and dellocation must only happen once.
3132+
31053133
The exporter MUST use the :c:member:`~Py_buffer.internal` field to keep
31063134
track of buffer-specific resources. This field is guaranteed to remain
31073135
constant, while a consumer MAY pass a copy of the original buffer as the

Doc/library/stdtypes.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5045,6 +5045,9 @@ copying.
50455045

50465046
.. versionadded:: 3.3
50475047

5048+
For information on the thread safety of :class:`memoryview` objects in
5049+
the :term:`free-threaded build`, see :ref:`thread-safety-memoryview`.
5050+
50485051

50495052
.. _types-set:
50505053

Doc/library/threadsafety.rst

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -548,3 +548,59 @@ Thread safety for bytearray objects
548548
549549
Consider external synchronization when sharing :class:`bytearray` instances
550550
across threads. See :ref:`freethreading-python-howto` for more information.
551+
552+
553+
.. _thread-safety-memoryview:
554+
555+
Thread safety for memoryview objects
556+
====================================
557+
558+
:class:`memoryview` objects provide access to the internal data of an
559+
underlying object without copying. Thread safety depends on both the
560+
memoryview itself and the underlying buffer exporter.
561+
562+
The memoryview implementation uses atomic operations to track its own
563+
exports in the :term:`free-threaded build`. Creating and
564+
releasing a memoryview are thread-safe. Attribute access (e.g.,
565+
:attr:`~memoryview.shape`, :attr:`~memoryview.format`) reads fields that
566+
are immutable for the lifetime of the memoryview, so concurrent reads
567+
are safe as long as the memoryview has not been released.
568+
569+
However, the actual data accessed through the memoryview is owned by the
570+
underlying object. Concurrent access to this data is only safe if the
571+
underlying object supports it:
572+
573+
* For immutable objects like :class:`bytes`, concurrent reads through
574+
multiple memoryviews are safe.
575+
576+
* For mutable objects like :class:`bytearray`, reading and writing the
577+
same memory region from multiple threads without external
578+
synchronization is not safe and may result in data corruption.
579+
Note that even read-only memoryviews of mutable objects do not
580+
prevent data races if the underlying object is modified from
581+
another thread.
582+
583+
.. code-block::
584+
:class: bad
585+
586+
# NOT safe: concurrent writes to the same buffer
587+
data = bytearray(1000)
588+
view = memoryview(data)
589+
# Thread 1: view[0:500] = b'x' * 500
590+
# Thread 2: view[0:500] = b'y' * 500
591+
592+
.. code-block::
593+
:class: good
594+
595+
# Safe: use a lock for concurrent access
596+
import threading
597+
lock = threading.Lock()
598+
data = bytearray(1000)
599+
view = memoryview(data)
600+
601+
with lock:
602+
view[0:500] = b'x' * 500
603+
604+
Resizing or reallocating the underlying object (such as calling
605+
:meth:`bytearray.resize`) while a memoryview is exported raises
606+
:exc:`BufferError`. This is enforced regardless of threading.

Doc/library/wave.rst

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,19 @@
99
--------------
1010

1111
The :mod:`!wave` module provides a convenient interface to the Waveform Audio
12-
"WAVE" (or "WAV") file format. Only uncompressed PCM encoded wave files are
13-
supported.
12+
"WAVE" (or "WAV") file format.
13+
14+
The module supports uncompressed PCM and IEEE floating-point WAV formats.
1415

1516
.. versionchanged:: 3.12
1617

1718
Support for ``WAVE_FORMAT_EXTENSIBLE`` headers was added, provided that the
1819
extended format is ``KSDATAFORMAT_SUBTYPE_PCM``.
1920

21+
.. versionchanged:: next
22+
23+
Support for reading and writing ``WAVE_FORMAT_IEEE_FLOAT`` files was added.
24+
2025
The :mod:`!wave` module defines the following function and exception:
2126

2227

@@ -60,6 +65,21 @@ The :mod:`!wave` module defines the following function and exception:
6065
specification or hits an implementation deficiency.
6166

6267

68+
.. data:: WAVE_FORMAT_PCM
69+
70+
Format code for uncompressed PCM audio.
71+
72+
73+
.. data:: WAVE_FORMAT_IEEE_FLOAT
74+
75+
Format code for IEEE floating-point audio.
76+
77+
78+
.. data:: WAVE_FORMAT_EXTENSIBLE
79+
80+
Format code for WAVE extensible headers.
81+
82+
6383
.. _wave-read-objects:
6484

6585
Wave_read Objects
@@ -98,6 +118,14 @@ Wave_read Objects
98118
Returns number of audio frames.
99119

100120

121+
.. method:: getformat()
122+
123+
Returns the frame format code.
124+
125+
This is one of :data:`WAVE_FORMAT_PCM`,
126+
:data:`WAVE_FORMAT_IEEE_FLOAT`, or :data:`WAVE_FORMAT_EXTENSIBLE`.
127+
128+
101129
.. method:: getcomptype()
102130

103131
Returns compression type (``'NONE'`` is the only supported type).
@@ -112,8 +140,8 @@ Wave_read Objects
112140
.. method:: getparams()
113141

114142
Returns a :func:`~collections.namedtuple` ``(nchannels, sampwidth,
115-
framerate, nframes, comptype, compname)``, equivalent to output of the
116-
``get*()`` methods.
143+
framerate, nframes, comptype, compname)``, equivalent to output
144+
of the ``get*()`` methods.
117145

118146

119147
.. method:: readframes(n)
@@ -190,6 +218,9 @@ Wave_write Objects
190218

191219
Set the sample width to *n* bytes.
192220

221+
For :data:`WAVE_FORMAT_IEEE_FLOAT`, only 4-byte (32-bit) and
222+
8-byte (64-bit) sample widths are supported.
223+
193224

194225
.. method:: getsampwidth()
195226

@@ -238,11 +269,32 @@ Wave_write Objects
238269
Return the human-readable compression type name.
239270

240271

272+
.. method:: setformat(format)
273+
274+
Set the frame format code.
275+
276+
Supported values are :data:`WAVE_FORMAT_PCM` and
277+
:data:`WAVE_FORMAT_IEEE_FLOAT`.
278+
279+
When setting :data:`WAVE_FORMAT_IEEE_FLOAT`, the sample width must be
280+
4 or 8 bytes.
281+
282+
283+
.. method:: getformat()
284+
285+
Return the current frame format code.
286+
287+
241288
.. method:: setparams(tuple)
242289

243-
The *tuple* should be ``(nchannels, sampwidth, framerate, nframes, comptype,
244-
compname)``, with values valid for the ``set*()`` methods. Sets all
245-
parameters.
290+
The *tuple* should be
291+
``(nchannels, sampwidth, framerate, nframes, comptype, compname, format)``,
292+
with values valid for the ``set*()`` methods. Sets all parameters.
293+
294+
For backwards compatibility, a 6-item tuple without *format* is also
295+
accepted and defaults to :data:`WAVE_FORMAT_PCM`.
296+
297+
For ``format=WAVE_FORMAT_IEEE_FLOAT``, *sampwidth* must be 4 or 8.
246298

247299

248300
.. method:: getparams()
@@ -279,3 +331,6 @@ Wave_write Objects
279331
Note that it is invalid to set any parameters after calling :meth:`writeframes`
280332
or :meth:`writeframesraw`, and any attempt to do so will raise
281333
:exc:`wave.Error`.
334+
335+
For :data:`WAVE_FORMAT_IEEE_FLOAT` output, a ``fact`` chunk is written as
336+
required by the WAVE specification for non-PCM formats.

Doc/reference/datamodel.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3637,12 +3637,25 @@ implement the protocol in Python.
36373637
provides a convenient way to interpret the flags. The method must return
36383638
a :class:`memoryview` object.
36393639

3640+
**Thread safety:** In :term:`free-threaded <free threading>` Python,
3641+
implementations must manage any internal export counter using atomic
3642+
operations. The method must be safe to call concurrently from multiple
3643+
threads, and the returned buffer's underlying data must remain valid
3644+
until the corresponding :meth:`~object.__release_buffer__` call
3645+
completes. See :ref:`thread-safety-memoryview` for details.
3646+
36403647
.. method:: object.__release_buffer__(self, buffer)
36413648

36423649
Called when a buffer is no longer needed. The *buffer* argument is a
36433650
:class:`memoryview` object that was previously returned by
36443651
:meth:`~object.__buffer__`. The method must release any resources associated
36453652
with the buffer. This method should return ``None``.
3653+
3654+
**Thread safety:** In :term:`free-threaded <free threading>` Python,
3655+
any export counter decrement must use atomic operations. Resource
3656+
cleanup must be thread-safe, as the final release may race with
3657+
concurrent releases from other threads.
3658+
36463659
Buffer objects that do not need to perform any cleanup are not required
36473660
to implement this method.
36483661

Doc/whatsnew/3.15.rst

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1511,6 +1511,21 @@ typing
15111511
wave
15121512
----
15131513

1514+
* Added support for IEEE floating-point WAVE audio
1515+
(``WAVE_FORMAT_IEEE_FLOAT``) in :mod:`wave`.
1516+
1517+
* Added :meth:`wave.Wave_read.getformat`, :meth:`wave.Wave_write.getformat`,
1518+
and :meth:`wave.Wave_write.setformat` for explicit frame format handling.
1519+
1520+
* :meth:`wave.Wave_write.setparams` accepts both 7-item tuples including
1521+
``format`` and 6-item tuples for backwards compatibility (defaulting to
1522+
``WAVE_FORMAT_PCM``).
1523+
1524+
* ``WAVE_FORMAT_IEEE_FLOAT`` output now includes a ``fact`` chunk,
1525+
as required for non-PCM WAVE formats.
1526+
1527+
(Contributed by Lionel Koenig and Michiel W. Beijen in :gh:`60729`.)
1528+
15141529
* Removed the ``getmark()``, ``setmark()`` and ``getmarkers()`` methods
15151530
of the :class:`~wave.Wave_read` and :class:`~wave.Wave_write` classes,
15161531
which were deprecated since Python 3.13.
25.9 KB
Binary file not shown.

Lib/test/audiotests.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,17 +27,18 @@ def tearDown(self):
2727
unlink(TESTFN)
2828

2929
def check_params(self, f, nchannels, sampwidth, framerate, nframes,
30-
comptype, compname):
30+
comptype, compname, format):
3131
self.assertEqual(f.getnchannels(), nchannels)
3232
self.assertEqual(f.getsampwidth(), sampwidth)
3333
self.assertEqual(f.getframerate(), framerate)
3434
self.assertEqual(f.getnframes(), nframes)
3535
self.assertEqual(f.getcomptype(), comptype)
3636
self.assertEqual(f.getcompname(), compname)
37+
self.assertEqual(f.getformat(), format)
3738

3839
params = f.getparams()
3940
self.assertEqual(params,
40-
(nchannels, sampwidth, framerate, nframes, comptype, compname))
41+
(nchannels, sampwidth, framerate, nframes, comptype, compname))
4142
self.assertEqual(params.nchannels, nchannels)
4243
self.assertEqual(params.sampwidth, sampwidth)
4344
self.assertEqual(params.framerate, framerate)
@@ -51,13 +52,17 @@ def check_params(self, f, nchannels, sampwidth, framerate, nframes,
5152

5253

5354
class AudioWriteTests(AudioTests):
55+
readonly = False
5456

5557
def create_file(self, testfile):
58+
if self.readonly:
59+
self.skipTest('Read only file format')
5660
f = self.fout = self.module.open(testfile, 'wb')
5761
f.setnchannels(self.nchannels)
5862
f.setsampwidth(self.sampwidth)
5963
f.setframerate(self.framerate)
6064
f.setcomptype(self.comptype, self.compname)
65+
f.setformat(self.format)
6166
return f
6267

6368
def check_file(self, testfile, nframes, frames):
@@ -67,13 +72,14 @@ def check_file(self, testfile, nframes, frames):
6772
self.assertEqual(f.getframerate(), self.framerate)
6873
self.assertEqual(f.getnframes(), nframes)
6974
self.assertEqual(f.readframes(nframes), frames)
75+
self.assertEqual(f.getformat(), self.format)
7076

7177
def test_write_params(self):
7278
f = self.create_file(TESTFN)
7379
f.setnframes(self.nframes)
7480
f.writeframes(self.frames)
7581
self.check_params(f, self.nchannels, self.sampwidth, self.framerate,
76-
self.nframes, self.comptype, self.compname)
82+
self.nframes, self.comptype, self.compname, self.format)
7783
f.close()
7884

7985
def test_write_context_manager_calls_close(self):
@@ -257,7 +263,7 @@ def test_read_params(self):
257263
f = self.f = self.module.open(self.sndfilepath)
258264
#self.assertEqual(f.getfp().name, self.sndfilepath)
259265
self.check_params(f, self.nchannels, self.sampwidth, self.framerate,
260-
self.sndfilenframes, self.comptype, self.compname)
266+
self.sndfilenframes, self.comptype, self.compname, self.format)
261267

262268
def test_close(self):
263269
with open(self.sndfilepath, 'rb') as testfile:
@@ -298,6 +304,8 @@ def test_read(self):
298304
f.setpos(f.getnframes() + 1)
299305

300306
def test_copy(self):
307+
if self.readonly:
308+
self.skipTest('Read only file format')
301309
f = self.f = self.module.open(self.sndfilepath)
302310
fout = self.fout = self.module.open(TESTFN, 'wb')
303311
fout.setparams(f.getparams())

Lib/test/test_functools.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,19 @@ def __repr__(self):
565565
g_partial = functools.partial(func, trigger, None, None, None, None, arg=None)
566566
self.assertEqual(repr(g_partial),"functools.partial(Function(old_function), EvilObject, None, None, None, None, arg=None)")
567567

568+
def test_str_subclass_error(self):
569+
class BadStr(str):
570+
def __eq__(self, other):
571+
raise RuntimeError
572+
def __hash__(self):
573+
return str.__hash__(self)
574+
575+
def f(**kwargs):
576+
return kwargs
568577

578+
p = functools.partial(f, poison="")
579+
with self.assertRaises(RuntimeError):
580+
result = p(**{BadStr("poison"): "new_value"})
569581

570582
@unittest.skipUnless(c_functools, 'requires the C _functools module')
571583
class TestPartialC(TestPartial, unittest.TestCase):

0 commit comments

Comments
 (0)