Skip to content

Commit 3f54d1c

Browse files
committed
Merge branch 'main' of https://github.com/python/cpython
2 parents 128fbdf + a53cc3f commit 3f54d1c

File tree

11 files changed

+239
-162
lines changed

11 files changed

+239
-162
lines changed

.github/workflows/stale.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ name: Mark stale pull requests
22

33
on:
44
schedule:
5-
- cron: "0 0 * * *"
5+
- cron: "0 */12 * * *"
66

77
permissions:
88
pull-requests: write

Doc/glossary.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -841,10 +841,11 @@ Glossary
841841
Some named tuples are built-in types (such as the above examples).
842842
Alternatively, a named tuple can be created from a regular class
843843
definition that inherits from :class:`tuple` and that defines named
844-
fields. Such a class can be written by hand or it can be created with
845-
the factory function :func:`collections.namedtuple`. The latter
846-
technique also adds some extra methods that may not be found in
847-
hand-written or built-in named tuples.
844+
fields. Such a class can be written by hand, or it can be created by
845+
inheriting :class:`typing.NamedTuple`, or with the factory function
846+
:func:`collections.namedtuple`. The latter techniques also add some
847+
extra methods that may not be found in hand-written or built-in named
848+
tuples.
848849

849850
namespace
850851
The place where a variable is stored. Namespaces are implemented as

Doc/library/itertools.rst

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -998,7 +998,7 @@ The following recipes have a more mathematical flavor:
998998

999999
def sum_of_squares(it):
10001000
"Add up the squares of the input values."
1001-
# sum_of_squares([10, 20, 30]) -> 1400
1001+
# sum_of_squares([10, 20, 30]) --> 1400
10021002
return math.sumprod(*tee(it))
10031003
10041004
def reshape(matrix, cols):
@@ -1019,17 +1019,16 @@ The following recipes have a more mathematical flavor:
10191019

10201020
def convolve(signal, kernel):
10211021
"""Discrete linear convolution of two iterables.
1022+
Equivalent to polynomial multiplication.
10221023

1023-
The kernel is fully consumed before the calculations begin.
1024-
The signal is consumed lazily and can be infinite.
1025-
1026-
Convolutions are mathematically commutative.
1027-
If the signal and kernel are swapped,
1028-
the output will be the same.
1024+
Convolutions are mathematically commutative; however, the inputs are
1025+
evaluated differently. The signal is consumed lazily and can be
1026+
infinite. The kernel is fully consumed before the calculations begin.
10291027

10301028
Article: https://betterexplained.com/articles/intuitive-convolution/
10311029
Video: https://www.youtube.com/watch?v=KuXjwB4LzSA
10321030
"""
1031+
# convolve([1, -1, -20], [1, -3]) --> 1 -4 -17 60
10331032
# convolve(data, [0.25, 0.25, 0.25, 0.25]) --> Moving average (blur)
10341033
# convolve(data, [1/2, 0, -1/2]) --> 1st derivative estimate
10351034
# convolve(data, [1, -2, 1]) --> 2nd derivative estimate
@@ -1067,7 +1066,7 @@ The following recipes have a more mathematical flavor:
10671066
f(x) = x³ -4x² -17x + 60
10681067
f'(x) = 3x² -8x -17
10691068
"""
1070-
# polynomial_derivative([1, -4, -17, 60]) -> [3, -8, -17]
1069+
# polynomial_derivative([1, -4, -17, 60]) --> [3, -8, -17]
10711070
n = len(coefficients)
10721071
powers = reversed(range(1, n))
10731072
return list(map(operator.mul, coefficients, powers))
@@ -1169,6 +1168,12 @@ The following recipes have a more mathematical flavor:
11691168

11701169
>>> take(10, count())
11711170
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
1171+
>>> # Verify that the input is consumed lazily
1172+
>>> it = iter('abcdef')
1173+
>>> take(3, it)
1174+
['a', 'b', 'c']
1175+
>>> list(it)
1176+
['d', 'e', 'f']
11721177

11731178
>>> list(prepend(1, [2, 3, 4]))
11741179
[1, 2, 3, 4]
@@ -1181,25 +1186,45 @@ The following recipes have a more mathematical flavor:
11811186

11821187
>>> list(tail(3, 'ABCDEFG'))
11831188
['E', 'F', 'G']
1189+
>>> # Verify the input is consumed greedily
1190+
>>> input_iterator = iter('ABCDEFG')
1191+
>>> output_iterator = tail(3, input_iterator)
1192+
>>> list(input_iterator)
1193+
[]
11841194

11851195
>>> it = iter(range(10))
11861196
>>> consume(it, 3)
1197+
>>> # Verify the input is consumed lazily
11871198
>>> next(it)
11881199
3
1200+
>>> # Verify the input is consumed completely
11891201
>>> consume(it)
11901202
>>> next(it, 'Done')
11911203
'Done'
11921204

11931205
>>> nth('abcde', 3)
11941206
'd'
1195-
11961207
>>> nth('abcde', 9) is None
11971208
True
1209+
>>> # Verify that the input is consumed lazily
1210+
>>> it = iter('abcde')
1211+
>>> nth(it, 2)
1212+
'c'
1213+
>>> list(it)
1214+
['d', 'e']
11981215

11991216
>>> [all_equal(s) for s in ('', 'A', 'AAAA', 'AAAB', 'AAABA')]
12001217
[True, True, True, False, False]
12011218
>>> [all_equal(s, key=str.casefold) for s in ('', 'A', 'AaAa', 'AAAB', 'AAABA')]
12021219
[True, True, True, False, False]
1220+
>>> # Verify that the input is consumed lazily and that only
1221+
>>> # one element of a second equivalence class is used to disprove
1222+
>>> # the assertion that all elements are equal.
1223+
>>> it = iter('aaabbbccc')
1224+
>>> all_equal(it)
1225+
False
1226+
>>> ''.join(it)
1227+
'bbccc'
12031228

12041229
>>> quantify(range(99), lambda x: x%2==0)
12051230
50
@@ -1222,6 +1247,11 @@ The following recipes have a more mathematical flavor:
12221247

12231248
>>> list(ncycles('abc', 3))
12241249
['a', 'b', 'c', 'a', 'b', 'c', 'a', 'b', 'c']
1250+
>>> # Verify greedy consumption of input iterator
1251+
>>> input_iterator = iter('abc')
1252+
>>> output_iterator = ncycles(input_iterator, 3)
1253+
>>> list(input_iterator)
1254+
[]
12251255

12261256
>>> sum_of_squares([10, 20, 30])
12271257
1400
@@ -1248,19 +1278,41 @@ The following recipes have a more mathematical flavor:
12481278

12491279
>>> list(transpose([(1, 2, 3), (11, 22, 33)]))
12501280
[(1, 11), (2, 22), (3, 33)]
1281+
>>> # Verify that the inputs are consumed lazily
1282+
>>> input1 = iter([1, 2, 3])
1283+
>>> input2 = iter([11, 22, 33])
1284+
>>> output_iterator = transpose([input1, input2])
1285+
>>> next(output_iterator)
1286+
(1, 11)
1287+
>>> list(zip(input1, input2))
1288+
[(2, 22), (3, 33)]
12511289

12521290
>>> list(matmul([(7, 5), (3, 5)], [[2, 5], [7, 9]]))
12531291
[(49, 80), (41, 60)]
12541292
>>> list(matmul([[2, 5], [7, 9], [3, 4]], [[7, 11, 5, 4, 9], [3, 5, 2, 6, 3]]))
12551293
[(29, 47, 20, 38, 33), (76, 122, 53, 82, 90), (33, 53, 23, 36, 39)]
12561294

1295+
>>> list(convolve([1, -1, -20], [1, -3])) == [1, -4, -17, 60]
1296+
True
12571297
>>> data = [20, 40, 24, 32, 20, 28, 16]
12581298
>>> list(convolve(data, [0.25, 0.25, 0.25, 0.25]))
12591299
[5.0, 15.0, 21.0, 29.0, 29.0, 26.0, 24.0, 16.0, 11.0, 4.0]
12601300
>>> list(convolve(data, [1, -1]))
12611301
[20, 20, -16, 8, -12, 8, -12, -16]
12621302
>>> list(convolve(data, [1, -2, 1]))
12631303
[20, 0, -36, 24, -20, 20, -20, -4, 16]
1304+
>>> # Verify signal is consumed lazily and the kernel greedily
1305+
>>> signal_iterator = iter([10, 20, 30, 40, 50])
1306+
>>> kernel_iterator = iter([1, 2, 3])
1307+
>>> output_iterator = convolve(signal_iterator, kernel_iterator)
1308+
>>> list(kernel_iterator)
1309+
[]
1310+
>>> next(output_iterator)
1311+
10
1312+
>>> next(output_iterator)
1313+
40
1314+
>>> list(signal_iterator)
1315+
[30, 40, 50]
12641316

12651317
>>> from fractions import Fraction
12661318
>>> from decimal import Decimal
@@ -1348,6 +1400,17 @@ The following recipes have a more mathematical flavor:
13481400
>>> # Test list input. Lists do not support None for the stop argument
13491401
>>> list(iter_index(list('AABCADEAF'), 'A'))
13501402
[0, 1, 4, 7]
1403+
>>> # Verify that input is consumed lazily
1404+
>>> input_iterator = iter('AABCADEAF')
1405+
>>> output_iterator = iter_index(input_iterator, 'A')
1406+
>>> next(output_iterator)
1407+
0
1408+
>>> next(output_iterator)
1409+
1
1410+
>>> next(output_iterator)
1411+
4
1412+
>>> ''.join(input_iterator)
1413+
'DEAF'
13511414

13521415
>>> list(sieve(30))
13531416
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
@@ -1499,6 +1562,17 @@ The following recipes have a more mathematical flavor:
14991562
[0, 2, 4, 6, 8]
15001563
>>> list(odds)
15011564
[1, 3, 5, 7, 9]
1565+
>>> # Verify that the input is consumed lazily
1566+
>>> input_iterator = iter(range(10))
1567+
>>> evens, odds = partition(is_odd, input_iterator)
1568+
>>> next(odds)
1569+
1
1570+
>>> next(odds)
1571+
3
1572+
>>> next(evens)
1573+
0
1574+
>>> list(input_iterator)
1575+
[4, 5, 6, 7, 8, 9]
15021576

15031577
>>> list(subslices('ABCD'))
15041578
['A', 'AB', 'ABC', 'ABCD', 'B', 'BC', 'BCD', 'C', 'CD', 'D']
@@ -1518,13 +1592,27 @@ The following recipes have a more mathematical flavor:
15181592
['A', 'B', 'C', 'D']
15191593
>>> list(unique_everseen('ABBcCAD', str.casefold))
15201594
['A', 'B', 'c', 'D']
1595+
>>> # Verify that the input is consumed lazily
1596+
>>> input_iterator = iter('AAAABBBCCDAABBB')
1597+
>>> output_iterator = unique_everseen(input_iterator)
1598+
>>> next(output_iterator)
1599+
'A'
1600+
>>> ''.join(input_iterator)
1601+
'AAABBBCCDAABBB'
15211602

15221603
>>> list(unique_justseen('AAAABBBCCDAABBB'))
15231604
['A', 'B', 'C', 'D', 'A', 'B']
15241605
>>> list(unique_justseen('ABBCcAD', str.casefold))
15251606
['A', 'B', 'C', 'A', 'D']
15261607
>>> list(unique_justseen('ABBcCAD', str.casefold))
15271608
['A', 'B', 'c', 'A', 'D']
1609+
>>> # Verify that the input is consumed lazily
1610+
>>> input_iterator = iter('AAAABBBCCDAABBB')
1611+
>>> output_iterator = unique_justseen(input_iterator)
1612+
>>> next(output_iterator)
1613+
'A'
1614+
>>> ''.join(input_iterator)
1615+
'AAABBBCCDAABBB'
15281616

15291617
>>> d = dict(a=1, b=2, c=3)
15301618
>>> it = iter_except(d.popitem, KeyError)
@@ -1545,6 +1633,12 @@ The following recipes have a more mathematical flavor:
15451633

15461634
>>> first_true('ABC0DEF1', '9', str.isdigit)
15471635
'0'
1636+
>>> # Verify that inputs are consumed lazily
1637+
>>> it = iter('ABC0DEF1')
1638+
>>> first_true(it, predicate=str.isdigit)
1639+
'0'
1640+
>>> ''.join(it)
1641+
'DEF1'
15481642

15491643

15501644
.. testcode::

Doc/library/typing.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1233,6 +1233,10 @@ These can be used as types in annotations. They all support subscription using
12331233

12341234
.. versionadded:: 3.5.3
12351235

1236+
.. versionchanged:: 3.13
1237+
1238+
:data:`ClassVar` can now be nested in :data:`Final` and vice versa.
1239+
12361240
.. data:: Final
12371241

12381242
Special typing construct to indicate final names to type checkers.
@@ -1256,6 +1260,10 @@ These can be used as types in annotations. They all support subscription using
12561260

12571261
.. versionadded:: 3.8
12581262

1263+
.. versionchanged:: 3.13
1264+
1265+
:data:`Final` can now be nested in :data:`ClassVar` and vice versa.
1266+
12591267
.. data:: Required
12601268

12611269
Special typing construct to mark a :class:`TypedDict` key as required.

Lib/test/support/import_helper.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,18 @@ def modules_cleanup(oldmodules):
268268
sys.modules.update(oldmodules)
269269

270270

271+
@contextlib.contextmanager
272+
def isolated_modules():
273+
"""
274+
Save modules on entry and cleanup on exit.
275+
"""
276+
(saved,) = modules_setup()
277+
try:
278+
yield
279+
finally:
280+
modules_cleanup(saved)
281+
282+
271283
def mock_register_at_fork(func):
272284
# bpo-30599: Mock os.register_at_fork() when importing the random module,
273285
# since this function doesn't allow to unregister callbacks and would leak

Lib/test/test_frame.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -293,71 +293,6 @@ def gen():
293293
""")
294294
assert_python_ok("-c", code)
295295

296-
@support.cpython_only
297-
@unittest.skipIf(Py_GIL_DISABLED, "test requires precise GC scheduling")
298-
def test_sneaky_frame_object(self):
299-
300-
def trace(frame, event, arg):
301-
"""
302-
Don't actually do anything, just force a frame object to be created.
303-
"""
304-
305-
def callback(phase, info):
306-
"""
307-
Yo dawg, I heard you like frames, so I'm allocating a frame while
308-
you're allocating a frame, so you can have a frame while you have a
309-
frame!
310-
"""
311-
nonlocal sneaky_frame_object
312-
sneaky_frame_object = sys._getframe().f_back.f_back
313-
# We're done here:
314-
gc.callbacks.remove(callback)
315-
316-
def f():
317-
while True:
318-
yield
319-
320-
old_threshold = gc.get_threshold()
321-
old_callbacks = gc.callbacks[:]
322-
old_enabled = gc.isenabled()
323-
old_trace = sys.gettrace()
324-
try:
325-
# Stop the GC for a second while we set things up:
326-
gc.disable()
327-
# Create a paused generator:
328-
g = f()
329-
next(g)
330-
# Move all objects to the oldest generation, and tell the GC to run
331-
# on the *very next* allocation:
332-
gc.collect()
333-
gc.set_threshold(1, 0, 0)
334-
sys._clear_internal_caches()
335-
# Okay, so here's the nightmare scenario:
336-
# - We're tracing the resumption of a generator, which creates a new
337-
# frame object.
338-
# - The allocation of this frame object triggers a collection
339-
# *before* the frame object is actually created.
340-
# - During the collection, we request the exact same frame object.
341-
# This test does it with a GC callback, but in real code it would
342-
# likely be a trace function, weakref callback, or finalizer.
343-
# - The collection finishes, and the original frame object is
344-
# created. We now have two frame objects fighting over ownership
345-
# of the same interpreter frame!
346-
sys.settrace(trace)
347-
gc.callbacks.append(callback)
348-
sneaky_frame_object = None
349-
gc.enable()
350-
next(g)
351-
# g.gi_frame should be the frame object from the callback (the
352-
# one that was *requested* second, but *created* first):
353-
self.assertIs(g.gi_frame, sneaky_frame_object)
354-
finally:
355-
gc.set_threshold(*old_threshold)
356-
gc.callbacks[:] = old_callbacks
357-
sys.settrace(old_trace)
358-
if old_enabled:
359-
gc.enable()
360-
361296
@support.cpython_only
362297
@threading_helper.requires_working_threading()
363298
def test_sneaky_frame_object_teardown(self):

Lib/test/test_importlib/resources/test_files.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def setUp(self):
7070
self.addCleanup(self.fixtures.close)
7171
self.site_dir = self.fixtures.enter_context(os_helper.temp_dir())
7272
self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir))
73-
self.fixtures.enter_context(import_helper.CleanImport())
73+
self.fixtures.enter_context(import_helper.isolated_modules())
7474

7575

7676
class ModulesFilesTests(SiteDir, unittest.TestCase):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Added import helper ``isolated_modules`` as ``CleanImport`` does not remove
2+
modules imported during the context. Use it in importlib.resources tests to
3+
avoid leaving ``mod`` around to impede importlib.metadata tests.

0 commit comments

Comments
 (0)