Skip to content

Commit 05189f3

Browse files
[3.12] GH-101588: Deprecate pickle/copy/deepcopy support in itertools (GH-104965) (GH-104997)
1 parent 305d78b commit 05189f3

File tree

4 files changed

+97
-3
lines changed

4 files changed

+97
-3
lines changed

Doc/whatsnew/3.12.rst

+6
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,12 @@ Pending Removal in Python 3.14
10691069
functions that have been deprecated since Python 2 but only gained a
10701070
proper :exc:`DeprecationWarning` in 3.12. Remove them in 3.14.
10711071

1072+
* :mod:`itertools` had undocumented, inefficient, historically buggy,
1073+
and inconsistent support for copy, deepcopy, and pickle operations.
1074+
This will be removed in 3.14 for a significant reduction in code
1075+
volume and maintenance burden.
1076+
(Contributed by Raymond Hettinger in :gh:`101588`.)
1077+
10721078
* Accessing ``co_lnotab`` was deprecated in :pep:`626` since 3.10
10731079
and was planned to be removed in 3.12
10741080
but it only got a proper :exc:`DeprecationWarning` in 3.12.

Lib/test/test_itertools.py

+47-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,26 @@
1515
import struct
1616
import threading
1717
import gc
18+
import warnings
19+
20+
def pickle_deprecated(testfunc):
21+
""" Run the test three times.
22+
First, verify that a Deprecation Warning is raised.
23+
Second, run normally but with DeprecationWarnings temporarily disabled.
24+
Third, run with warnings promoted to errors.
25+
"""
26+
def inner(self):
27+
with self.assertWarns(DeprecationWarning):
28+
testfunc(self)
29+
with warnings.catch_warnings():
30+
warnings.simplefilter("ignore", category=DeprecationWarning)
31+
testfunc(self)
32+
with warnings.catch_warnings():
33+
warnings.simplefilter("error", category=DeprecationWarning)
34+
with self.assertRaises((DeprecationWarning, AssertionError, SystemError)):
35+
testfunc(self)
36+
37+
return inner
1838

1939
maxsize = support.MAX_Py_ssize_t
2040
minsize = -maxsize-1
@@ -124,6 +144,7 @@ def expand(it, i=0):
124144
c = expand(compare[took:])
125145
self.assertEqual(a, c);
126146

147+
@pickle_deprecated
127148
def test_accumulate(self):
128149
self.assertEqual(list(accumulate(range(10))), # one positional arg
129150
[0, 1, 3, 6, 10, 15, 21, 28, 36, 45])
@@ -220,6 +241,7 @@ def test_chain_from_iterable(self):
220241
self.assertRaises(TypeError, list, chain.from_iterable([2, 3]))
221242
self.assertEqual(list(islice(chain.from_iterable(repeat(range(5))), 2)), [0, 1])
222243

244+
@pickle_deprecated
223245
def test_chain_reducible(self):
224246
for oper in [copy.deepcopy] + picklecopiers:
225247
it = chain('abc', 'def')
@@ -233,6 +255,7 @@ def test_chain_reducible(self):
233255
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
234256
self.pickletest(proto, chain('abc', 'def'), compare=list('abcdef'))
235257

258+
@pickle_deprecated
236259
def test_chain_setstate(self):
237260
self.assertRaises(TypeError, chain().__setstate__, ())
238261
self.assertRaises(TypeError, chain().__setstate__, [])
@@ -246,6 +269,7 @@ def test_chain_setstate(self):
246269
it.__setstate__((iter(['abc', 'def']), iter(['ghi'])))
247270
self.assertEqual(list(it), ['ghi', 'a', 'b', 'c', 'd', 'e', 'f'])
248271

272+
@pickle_deprecated
249273
def test_combinations(self):
250274
self.assertRaises(TypeError, combinations, 'abc') # missing r argument
251275
self.assertRaises(TypeError, combinations, 'abc', 2, 1) # too many arguments
@@ -269,7 +293,6 @@ def test_combinations(self):
269293
self.assertEqual(list(op(testIntermediate)),
270294
[(0,1,3), (0,2,3), (1,2,3)])
271295

272-
273296
def combinations1(iterable, r):
274297
'Pure python version shown in the docs'
275298
pool = tuple(iterable)
@@ -337,6 +360,7 @@ def test_combinations_tuple_reuse(self):
337360
self.assertEqual(len(set(map(id, combinations('abcde', 3)))), 1)
338361
self.assertNotEqual(len(set(map(id, list(combinations('abcde', 3))))), 1)
339362

363+
@pickle_deprecated
340364
def test_combinations_with_replacement(self):
341365
cwr = combinations_with_replacement
342366
self.assertRaises(TypeError, cwr, 'abc') # missing r argument
@@ -425,6 +449,7 @@ def test_combinations_with_replacement_tuple_reuse(self):
425449
self.assertEqual(len(set(map(id, cwr('abcde', 3)))), 1)
426450
self.assertNotEqual(len(set(map(id, list(cwr('abcde', 3))))), 1)
427451

452+
@pickle_deprecated
428453
def test_permutations(self):
429454
self.assertRaises(TypeError, permutations) # too few arguments
430455
self.assertRaises(TypeError, permutations, 'abc', 2, 1) # too many arguments
@@ -531,6 +556,7 @@ def test_combinatorics(self):
531556
self.assertEqual(comb, list(filter(set(perm).__contains__, cwr))) # comb: cwr that is a perm
532557
self.assertEqual(comb, sorted(set(cwr) & set(perm))) # comb: both a cwr and a perm
533558

559+
@pickle_deprecated
534560
def test_compress(self):
535561
self.assertEqual(list(compress(data='ABCDEF', selectors=[1,0,1,0,1,1])), list('ACEF'))
536562
self.assertEqual(list(compress('ABCDEF', [1,0,1,0,1,1])), list('ACEF'))
@@ -564,7 +590,7 @@ def test_compress(self):
564590
next(testIntermediate)
565591
self.assertEqual(list(op(testIntermediate)), list(result2))
566592

567-
593+
@pickle_deprecated
568594
def test_count(self):
569595
self.assertEqual(lzip('abc',count()), [('a', 0), ('b', 1), ('c', 2)])
570596
self.assertEqual(lzip('abc',count(3)), [('a', 3), ('b', 4), ('c', 5)])
@@ -613,6 +639,7 @@ def test_count(self):
613639
#check proper internal error handling for large "step' sizes
614640
count(1, maxsize+5); sys.exc_info()
615641

642+
@pickle_deprecated
616643
def test_count_with_stride(self):
617644
self.assertEqual(lzip('abc',count(2,3)), [('a', 2), ('b', 5), ('c', 8)])
618645
self.assertEqual(lzip('abc',count(start=2,step=3)),
@@ -675,6 +702,7 @@ def test_cycle(self):
675702
self.assertRaises(TypeError, cycle, 5)
676703
self.assertEqual(list(islice(cycle(gen3()),10)), [0,1,2,0,1,2,0,1,2,0])
677704

705+
@pickle_deprecated
678706
def test_cycle_copy_pickle(self):
679707
# check copy, deepcopy, pickle
680708
c = cycle('abc')
@@ -711,6 +739,7 @@ def test_cycle_copy_pickle(self):
711739
d = pickle.loads(p) # rebuild the cycle object
712740
self.assertEqual(take(20, d), list('cdeabcdeabcdeabcdeab'))
713741

742+
@pickle_deprecated
714743
def test_cycle_unpickle_compat(self):
715744
testcases = [
716745
b'citertools\ncycle\n(c__builtin__\niter\n((lI1\naI2\naI3\natRI1\nbtR((lI1\naI0\ntb.',
@@ -742,6 +771,7 @@ def test_cycle_unpickle_compat(self):
742771
it = pickle.loads(t)
743772
self.assertEqual(take(10, it), [2, 3, 1, 2, 3, 1, 2, 3, 1, 2])
744773

774+
@pickle_deprecated
745775
def test_cycle_setstate(self):
746776
# Verify both modes for restoring state
747777

@@ -778,6 +808,7 @@ def test_cycle_setstate(self):
778808
self.assertRaises(TypeError, cycle('').__setstate__, ())
779809
self.assertRaises(TypeError, cycle('').__setstate__, ([],))
780810

811+
@pickle_deprecated
781812
def test_groupby(self):
782813
# Check whether it accepts arguments correctly
783814
self.assertEqual([], list(groupby([])))
@@ -935,6 +966,7 @@ def test_filter(self):
935966
c = filter(isEven, range(6))
936967
self.pickletest(proto, c)
937968

969+
@pickle_deprecated
938970
def test_filterfalse(self):
939971
self.assertEqual(list(filterfalse(isEven, range(6))), [1,3,5])
940972
self.assertEqual(list(filterfalse(None, [0,1,0,2,0])), [0,0,0])
@@ -965,6 +997,7 @@ def test_zip(self):
965997
lzip('abc', 'def'))
966998

967999
@support.impl_detail("tuple reuse is specific to CPython")
1000+
@pickle_deprecated
9681001
def test_zip_tuple_reuse(self):
9691002
ids = list(map(id, zip('abc', 'def')))
9701003
self.assertEqual(min(ids), max(ids))
@@ -1040,6 +1073,7 @@ def test_zip_longest_tuple_reuse(self):
10401073
ids = list(map(id, list(zip_longest('abc', 'def'))))
10411074
self.assertEqual(len(dict.fromkeys(ids)), len(ids))
10421075

1076+
@pickle_deprecated
10431077
def test_zip_longest_pickling(self):
10441078
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
10451079
self.pickletest(proto, zip_longest("abc", "def"))
@@ -1186,6 +1220,7 @@ def test_product_tuple_reuse(self):
11861220
self.assertEqual(len(set(map(id, product('abc', 'def')))), 1)
11871221
self.assertNotEqual(len(set(map(id, list(product('abc', 'def'))))), 1)
11881222

1223+
@pickle_deprecated
11891224
def test_product_pickling(self):
11901225
# check copy, deepcopy, pickle
11911226
for args, result in [
@@ -1201,6 +1236,7 @@ def test_product_pickling(self):
12011236
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
12021237
self.pickletest(proto, product(*args))
12031238

1239+
@pickle_deprecated
12041240
def test_product_issue_25021(self):
12051241
# test that indices are properly clamped to the length of the tuples
12061242
p = product((1, 2),(3,))
@@ -1211,6 +1247,7 @@ def test_product_issue_25021(self):
12111247
p.__setstate__((0, 0, 0x1000)) # will access tuple element 1 if not clamped
12121248
self.assertRaises(StopIteration, next, p)
12131249

1250+
@pickle_deprecated
12141251
def test_repeat(self):
12151252
self.assertEqual(list(repeat(object='a', times=3)), ['a', 'a', 'a'])
12161253
self.assertEqual(lzip(range(3),repeat('a')),
@@ -1243,6 +1280,7 @@ def test_repeat_with_negative_times(self):
12431280
self.assertEqual(repr(repeat('a', times=-1)), "repeat('a', 0)")
12441281
self.assertEqual(repr(repeat('a', times=-2)), "repeat('a', 0)")
12451282

1283+
@pickle_deprecated
12461284
def test_map(self):
12471285
self.assertEqual(list(map(operator.pow, range(3), range(1,7))),
12481286
[0**1, 1**2, 2**3])
@@ -1273,6 +1311,7 @@ def test_map(self):
12731311
c = map(tupleize, 'abc', count())
12741312
self.pickletest(proto, c)
12751313

1314+
@pickle_deprecated
12761315
def test_starmap(self):
12771316
self.assertEqual(list(starmap(operator.pow, zip(range(3), range(1,7)))),
12781317
[0**1, 1**2, 2**3])
@@ -1300,6 +1339,7 @@ def test_starmap(self):
13001339
c = starmap(operator.pow, zip(range(3), range(1,7)))
13011340
self.pickletest(proto, c)
13021341

1342+
@pickle_deprecated
13031343
def test_islice(self):
13041344
for args in [ # islice(args) should agree with range(args)
13051345
(10, 20, 3),
@@ -1394,6 +1434,7 @@ def __index__(self):
13941434
self.assertEqual(list(islice(range(100), IntLike(10), IntLike(50), IntLike(5))),
13951435
list(range(10,50,5)))
13961436

1437+
@pickle_deprecated
13971438
def test_takewhile(self):
13981439
data = [1, 3, 5, 20, 2, 4, 6, 8]
13991440
self.assertEqual(list(takewhile(underten, data)), [1, 3, 5])
@@ -1414,6 +1455,7 @@ def test_takewhile(self):
14141455
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
14151456
self.pickletest(proto, takewhile(underten, data))
14161457

1458+
@pickle_deprecated
14171459
def test_dropwhile(self):
14181460
data = [1, 3, 5, 20, 2, 4, 6, 8]
14191461
self.assertEqual(list(dropwhile(underten, data)), [20, 2, 4, 6, 8])
@@ -1431,6 +1473,7 @@ def test_dropwhile(self):
14311473
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
14321474
self.pickletest(proto, dropwhile(underten, data))
14331475

1476+
@pickle_deprecated
14341477
def test_tee(self):
14351478
n = 200
14361479

@@ -1732,6 +1775,7 @@ class TestExamples(unittest.TestCase):
17321775
def test_accumulate(self):
17331776
self.assertEqual(list(accumulate([1,2,3,4,5])), [1, 3, 6, 10, 15])
17341777

1778+
@pickle_deprecated
17351779
def test_accumulate_reducible(self):
17361780
# check copy, deepcopy, pickle
17371781
data = [1, 2, 3, 4, 5]
@@ -1747,6 +1791,7 @@ def test_accumulate_reducible(self):
17471791
self.assertEqual(list(copy.deepcopy(it)), accumulated[1:])
17481792
self.assertEqual(list(copy.copy(it)), accumulated[1:])
17491793

1794+
@pickle_deprecated
17501795
def test_accumulate_reducible_none(self):
17511796
# Issue #25718: total is None
17521797
it = accumulate([None, None, None], operator.is_)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Deprecate undocumented copy/deepcopy/pickle support for itertools.

0 commit comments

Comments
 (0)