Skip to content

Commit 36bb885

Browse files
authored
Merge branch 'main' into pythongh-74033-remove
2 parents 36194a2 + fbe6a09 commit 36bb885

File tree

68 files changed

+911
-837
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+911
-837
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,7 @@ jobs:
486486
config_hash: ${{ needs.check_source.outputs.config_hash }}
487487
options: ./configure --config-cache --with-thread-sanitizer --with-pydebug
488488
suppressions_path: Tools/tsan/supressions.txt
489+
tsan_logs_artifact_name: tsan-logs-default
489490

490491
build_tsan_free_threading:
491492
name: 'Thread sanitizer (free-threading)'
@@ -496,6 +497,7 @@ jobs:
496497
config_hash: ${{ needs.check_source.outputs.config_hash }}
497498
options: ./configure --config-cache --disable-gil --with-thread-sanitizer --with-pydebug
498499
suppressions_path: Tools/tsan/suppressions_free_threading.txt
500+
tsan_logs_artifact_name: tsan-logs-free-threading
499501

500502
# CIFuzz job based on https://google.github.io/oss-fuzz/getting-started/continuous-integration/
501503
cifuzz:

.github/workflows/reusable-tsan.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ on:
1111
description: 'A repo relative path to the suppressions file'
1212
required: true
1313
type: string
14+
tsan_logs_artifact_name:
15+
description: 'Name of the TSAN logs artifact. Must be unique for each job.'
16+
required: true
17+
type: string
1418

1519
jobs:
1620
build_tsan_reusable:
@@ -41,7 +45,7 @@ jobs:
4145
sudo sysctl -w vm.mmap_rnd_bits=28
4246
- name: TSAN Option Setup
4347
run: |
44-
echo "TSAN_OPTIONS=suppressions=${GITHUB_WORKSPACE}/${{ inputs.suppressions_path }}" >> $GITHUB_ENV
48+
echo "TSAN_OPTIONS=log_path=${GITHUB_WORKSPACE}/tsan_log suppressions=${GITHUB_WORKSPACE}/${{ inputs.suppressions_path }} handle_segv=0" >> $GITHUB_ENV
4549
echo "CC=clang" >> $GITHUB_ENV
4650
echo "CXX=clang++" >> $GITHUB_ENV
4751
- name: Add ccache to PATH
@@ -60,3 +64,13 @@ jobs:
6064
run: make pythoninfo
6165
- name: Tests
6266
run: ./python -m test --tsan -j4
67+
- name: Display TSAN logs
68+
if: always()
69+
run: find ${GITHUB_WORKSPACE} -name 'tsan_log.*' | xargs head -n 1000
70+
- name: Archive TSAN logs
71+
if: always()
72+
uses: actions/upload-artifact@v4
73+
with:
74+
name: ${{ inputs.tsan_logs_artifact_name }}
75+
path: tsan_log.*
76+
if-no-files-found: ignore

Doc/library/functools.rst

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -646,8 +646,9 @@ The :mod:`functools` module defines the following functions:
646646
attributes of the wrapper function are updated with the corresponding attributes
647647
from the original function. The default values for these arguments are the
648648
module level constants ``WRAPPER_ASSIGNMENTS`` (which assigns to the wrapper
649-
function's ``__module__``, ``__name__``, ``__qualname__``, ``__annotations__``
650-
and ``__doc__``, the documentation string) and ``WRAPPER_UPDATES`` (which
649+
function's ``__module__``, ``__name__``, ``__qualname__``, ``__annotations__``,
650+
``__type_params__``, and ``__doc__``, the documentation string)
651+
and ``WRAPPER_UPDATES`` (which
651652
updates the wrapper function's ``__dict__``, i.e. the instance dictionary).
652653

653654
To allow access to the original function for introspection and other purposes
@@ -677,6 +678,9 @@ The :mod:`functools` module defines the following functions:
677678
function, even if that function defined a ``__wrapped__`` attribute.
678679
(see :issue:`17482`)
679680

681+
.. versionchanged:: 3.12
682+
The ``__type_params__`` attribute is now copied by default.
683+
680684

681685
.. decorator:: wraps(wrapped, assigned=WRAPPER_ASSIGNMENTS, updated=WRAPPER_UPDATES)
682686

Doc/library/itertools.rst

Lines changed: 31 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -122,15 +122,15 @@ loops that truncate the stream.
122122
# accumulate([1,2,3,4,5]) → 1 3 6 10 15
123123
# accumulate([1,2,3,4,5], initial=100) → 100 101 103 106 110 115
124124
# accumulate([1,2,3,4,5], operator.mul) → 1 2 6 24 120
125-
it = iter(iterable)
125+
iterator = iter(iterable)
126126
total = initial
127127
if initial is None:
128128
try:
129-
total = next(it)
129+
total = next(iterator)
130130
except StopIteration:
131131
return
132132
yield total
133-
for element in it:
133+
for element in iterator:
134134
total = func(total, element)
135135
yield total
136136

@@ -218,9 +218,8 @@ loops that truncate the stream.
218218

219219
def chain(*iterables):
220220
# chain('ABC', 'DEF') → A B C D E F
221-
for it in iterables:
222-
for element in it:
223-
yield element
221+
for iterable in iterables:
222+
yield from iterable
224223

225224

226225
.. classmethod:: chain.from_iterable(iterable)
@@ -230,9 +229,8 @@ loops that truncate the stream.
230229

231230
def from_iterable(iterables):
232231
# chain.from_iterable(['ABC', 'DEF']) → A B C D E F
233-
for it in iterables:
234-
for element in it:
235-
yield element
232+
for iterable in iterables:
233+
yield from iterable
236234

237235

238236
.. function:: combinations(iterable, r)
@@ -380,7 +378,7 @@ loops that truncate the stream.
380378
saved.append(element)
381379
while saved:
382380
for element in saved:
383-
yield element
381+
yield element
384382

385383
Note, this member of the toolkit may require significant auxiliary storage
386384
(depending on the length of the iterable).
@@ -615,10 +613,10 @@ loops that truncate the stream.
615613
This function is roughly equivalent to the following code, except that the
616614
actual implementation does not build up intermediate results in memory::
617615

618-
def product(*args, repeat=1):
616+
def product(*iterables, repeat=1):
619617
# product('ABCD', 'xy') → Ax Ay Bx By Cx Cy Dx Dy
620618
# product(range(2), repeat=3) → 000 001 010 011 100 101 110 111
621-
pools = [tuple(pool) for pool in args] * repeat
619+
pools = [tuple(pool) for pool in iterables] * repeat
622620
result = [[]]
623621
for pool in pools:
624622
result = [x+[y] for x in result for y in pool]
@@ -696,24 +694,22 @@ loops that truncate the stream.
696694

697695
Return *n* independent iterators from a single iterable.
698696

699-
The following Python code helps explain what *tee* does (although the actual
700-
implementation is more complex and uses only a single underlying
701-
:abbr:`FIFO (first-in, first-out)` queue)::
697+
Roughly equivalent to::
702698

703699
def tee(iterable, n=2):
704-
it = iter(iterable)
705-
deques = [collections.deque() for i in range(n)]
706-
def gen(mydeque):
707-
while True:
708-
if not mydeque: # when the local deque is empty
709-
try:
710-
newval = next(it) # fetch a new value and
711-
except StopIteration:
712-
return
713-
for d in deques: # load it to all the deques
714-
d.append(newval)
715-
yield mydeque.popleft()
716-
return tuple(gen(d) for d in deques)
700+
iterator = iter(iterable)
701+
empty_link = [None, None] # Singly linked list: [value, link]
702+
return tuple(_tee(iterator, empty_link) for _ in range(n))
703+
704+
def _tee(iterator, link):
705+
while True:
706+
if link[1] is None:
707+
try:
708+
link[:] = [next(iterator), [None, None]]
709+
except StopIteration:
710+
return
711+
value, link = link
712+
yield value
717713

718714
Once a :func:`tee` has been created, the original *iterable* should not be
719715
used anywhere else; otherwise, the *iterable* could get advanced without
@@ -735,17 +731,17 @@ loops that truncate the stream.
735731
iterables are of uneven length, missing values are filled-in with *fillvalue*.
736732
Iteration continues until the longest iterable is exhausted. Roughly equivalent to::
737733

738-
def zip_longest(*args, fillvalue=None):
734+
def zip_longest(*iterables, fillvalue=None):
739735
# zip_longest('ABCD', 'xy', fillvalue='-') → Ax By C- D-
740-
iterators = [iter(it) for it in args]
736+
iterators = [iter(it) for it in iterables]
741737
num_active = len(iterators)
742738
if not num_active:
743739
return
744740
while True:
745741
values = []
746-
for i, it in enumerate(iterators):
742+
for i, iterator in enumerate(iterators):
747743
try:
748-
value = next(it)
744+
value = next(iterator)
749745
except StopIteration:
750746
num_active -= 1
751747
if not num_active:
@@ -800,6 +796,7 @@ and :term:`generators <generator>` which incur interpreter overhead.
800796
.. testcode::
801797

802798
import collections
799+
import contextlib
803800
import functools
804801
import math
805802
import operator
@@ -942,32 +939,26 @@ and :term:`generators <generator>` which incur interpreter overhead.
942939
# iter_index('AABCADEAF', 'A') → 0 1 4 7
943940
seq_index = getattr(iterable, 'index', None)
944941
if seq_index is None:
945-
# Path for general iterables
946942
iterator = islice(iterable, start, stop)
947943
for i, element in enumerate(iterator, start):
948944
if element is value or element == value:
949945
yield i
950946
else:
951-
# Path for sequences with an index() method
952947
stop = len(iterable) if stop is None else stop
953948
i = start
954-
try:
949+
with contextlib.suppress(ValueError):
955950
while True:
956951
yield (i := seq_index(value, i, stop))
957952
i += 1
958-
except ValueError:
959-
pass
960953

961954
def iter_except(func, exception, first=None):
962955
"Convert a call-until-exception interface to an iterator interface."
963956
# iter_except(d.popitem, KeyError) → non-blocking dictionary iterator
964-
try:
957+
with contextlib.suppress(exception):
965958
if first is not None:
966959
yield first()
967960
while True:
968961
yield func()
969-
except exception:
970-
pass
971962

972963

973964
The following recipes have a more mathematical flavor:

Doc/library/pathlib.rst

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -873,7 +873,7 @@ Methods
873873
^^^^^^^
874874

875875
Concrete paths provide the following methods in addition to pure paths
876-
methods. Many of these methods can raise an :exc:`OSError` if a system
876+
methods. Some of these methods can raise an :exc:`OSError` if a system
877877
call fails (for example because the path doesn't exist).
878878

879879
.. versionchanged:: 3.8
@@ -885,6 +885,15 @@ call fails (for example because the path doesn't exist).
885885
instead of raising an exception for paths that contain characters
886886
unrepresentable at the OS level.
887887

888+
.. versionchanged:: 3.14
889+
890+
The methods given above now return ``False`` instead of raising any
891+
:exc:`OSError` exception from the operating system. In previous versions,
892+
some kinds of :exc:`OSError` exception are raised, and others suppressed.
893+
The new behaviour is consistent with :func:`os.path.exists`,
894+
:func:`os.path.isdir`, etc. Use :meth:`~Path.stat` to retrieve the file
895+
status without suppressing exceptions.
896+
888897

889898
.. classmethod:: Path.cwd()
890899

@@ -951,6 +960,8 @@ call fails (for example because the path doesn't exist).
951960
.. method:: Path.exists(*, follow_symlinks=True)
952961

953962
Return ``True`` if the path points to an existing file or directory.
963+
``False`` will be returned if the path is invalid, inaccessible or missing.
964+
Use :meth:`Path.stat` to distinguish between these cases.
954965

955966
This method normally follows symlinks; to check if a symlink exists, add
956967
the argument ``follow_symlinks=False``.
@@ -1067,11 +1078,10 @@ call fails (for example because the path doesn't exist).
10671078

10681079
.. method:: Path.is_dir(*, follow_symlinks=True)
10691080

1070-
Return ``True`` if the path points to a directory, ``False`` if it points
1071-
to another kind of file.
1072-
1073-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1074-
other errors (such as permission errors) are propagated.
1081+
Return ``True`` if the path points to a directory. ``False`` will be
1082+
returned if the path is invalid, inaccessible or missing, or if it points
1083+
to something other than a directory. Use :meth:`Path.stat` to distinguish
1084+
between these cases.
10751085

10761086
This method normally follows symlinks; to exclude symlinks to directories,
10771087
add the argument ``follow_symlinks=False``.
@@ -1082,11 +1092,10 @@ call fails (for example because the path doesn't exist).
10821092

10831093
.. method:: Path.is_file(*, follow_symlinks=True)
10841094

1085-
Return ``True`` if the path points to a regular file, ``False`` if it
1086-
points to another kind of file.
1087-
1088-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1089-
other errors (such as permission errors) are propagated.
1095+
Return ``True`` if the path points to a regular file. ``False`` will be
1096+
returned if the path is invalid, inaccessible or missing, or if it points
1097+
to something other than a regular file. Use :meth:`Path.stat` to
1098+
distinguish between these cases.
10901099

10911100
This method normally follows symlinks; to exclude symlinks, add the
10921101
argument ``follow_symlinks=False``.
@@ -1122,46 +1131,42 @@ call fails (for example because the path doesn't exist).
11221131

11231132
.. method:: Path.is_symlink()
11241133

1125-
Return ``True`` if the path points to a symbolic link, ``False`` otherwise.
1126-
1127-
``False`` is also returned if the path doesn't exist; other errors (such
1128-
as permission errors) are propagated.
1134+
Return ``True`` if the path points to a symbolic link, even if that symlink
1135+
is broken. ``False`` will be returned if the path is invalid, inaccessible
1136+
or missing, or if it points to something other than a symbolic link. Use
1137+
:meth:`Path.stat` to distinguish between these cases.
11291138

11301139

11311140
.. method:: Path.is_socket()
11321141

1133-
Return ``True`` if the path points to a Unix socket (or a symbolic link
1134-
pointing to a Unix socket), ``False`` if it points to another kind of file.
1135-
1136-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1137-
other errors (such as permission errors) are propagated.
1142+
Return ``True`` if the path points to a Unix socket. ``False`` will be
1143+
returned if the path is invalid, inaccessible or missing, or if it points
1144+
to something other than a Unix socket. Use :meth:`Path.stat` to
1145+
distinguish between these cases.
11381146

11391147

11401148
.. method:: Path.is_fifo()
11411149

1142-
Return ``True`` if the path points to a FIFO (or a symbolic link
1143-
pointing to a FIFO), ``False`` if it points to another kind of file.
1144-
1145-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1146-
other errors (such as permission errors) are propagated.
1150+
Return ``True`` if the path points to a FIFO. ``False`` will be returned if
1151+
the path is invalid, inaccessible or missing, or if it points to something
1152+
other than a FIFO. Use :meth:`Path.stat` to distinguish between these
1153+
cases.
11471154

11481155

11491156
.. method:: Path.is_block_device()
11501157

1151-
Return ``True`` if the path points to a block device (or a symbolic link
1152-
pointing to a block device), ``False`` if it points to another kind of file.
1153-
1154-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1155-
other errors (such as permission errors) are propagated.
1158+
Return ``True`` if the path points to a block device. ``False`` will be
1159+
returned if the path is invalid, inaccessible or missing, or if it points
1160+
to something other than a block device. Use :meth:`Path.stat` to
1161+
distinguish between these cases.
11561162

11571163

11581164
.. method:: Path.is_char_device()
11591165

1160-
Return ``True`` if the path points to a character device (or a symbolic link
1161-
pointing to a character device), ``False`` if it points to another kind of file.
1162-
1163-
``False`` is also returned if the path doesn't exist or is a broken symlink;
1164-
other errors (such as permission errors) are propagated.
1166+
Return ``True`` if the path points to a character device. ``False`` will be
1167+
returned if the path is invalid, inaccessible or missing, or if it points
1168+
to something other than a character device. Use :meth:`Path.stat` to
1169+
distinguish between these cases.
11651170

11661171

11671172
.. method:: Path.iterdir()

Doc/library/shutil.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ Directory and files operations
338338
before removing the junction.
339339

340340
.. versionchanged:: 3.11
341-
The *dir_fd* parameter.
341+
Added the *dir_fd* parameter.
342342

343343
.. versionchanged:: 3.12
344344
Added the *onexc* parameter, deprecated *onerror*.

0 commit comments

Comments
 (0)