Skip to content

[3.12] GH-101588: Deprecate pickle/copy/deepcopy support in itertools (GH-104965) #104997

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,12 @@ Pending Removal in Python 3.14
functions that have been deprecated since Python 2 but only gained a
proper :exc:`DeprecationWarning` in 3.12. Remove them in 3.14.

* :mod:`itertools` had undocumented, inefficient, historically buggy,
and inconsistent support for copy, deepcopy, and pickle operations.
This will be removed in 3.14 for a significant reduction in code
volume and maintenance burden.
(Contributed by Raymond Hettinger in :gh:`101588`.)

* Accessing ``co_lnotab`` was deprecated in :pep:`626` since 3.10
and was planned to be removed in 3.12
but it only got a proper :exc:`DeprecationWarning` in 3.12.
Expand Down
49 changes: 47 additions & 2 deletions Lib/test/test_itertools.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@
import struct
import threading
import gc
import warnings

def pickle_deprecated(testfunc):
""" Run the test three times.
First, verify that a Deprecation Warning is raised.
Second, run normally but with DeprecationWarnings temporarily disabled.
Third, run with warnings promoted to errors.
"""
def inner(self):
with self.assertWarns(DeprecationWarning):
testfunc(self)
with warnings.catch_warnings():
warnings.simplefilter("ignore", category=DeprecationWarning)
testfunc(self)
with warnings.catch_warnings():
warnings.simplefilter("error", category=DeprecationWarning)
with self.assertRaises((DeprecationWarning, AssertionError, SystemError)):
testfunc(self)

return inner

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

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

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

@pickle_deprecated
def test_chain_setstate(self):
self.assertRaises(TypeError, chain().__setstate__, ())
self.assertRaises(TypeError, chain().__setstate__, [])
Expand All @@ -246,6 +269,7 @@ def test_chain_setstate(self):
it.__setstate__((iter(['abc', 'def']), iter(['ghi'])))
self.assertEqual(list(it), ['ghi', 'a', 'b', 'c', 'd', 'e', 'f'])

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


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

@pickle_deprecated
def test_combinations_with_replacement(self):
cwr = combinations_with_replacement
self.assertRaises(TypeError, cwr, 'abc') # missing r argument
Expand Down Expand Up @@ -425,6 +449,7 @@ def test_combinations_with_replacement_tuple_reuse(self):
self.assertEqual(len(set(map(id, cwr('abcde', 3)))), 1)
self.assertNotEqual(len(set(map(id, list(cwr('abcde', 3))))), 1)

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

@pickle_deprecated
def test_compress(self):
self.assertEqual(list(compress(data='ABCDEF', selectors=[1,0,1,0,1,1])), list('ACEF'))
self.assertEqual(list(compress('ABCDEF', [1,0,1,0,1,1])), list('ACEF'))
Expand Down Expand Up @@ -564,7 +590,7 @@ def test_compress(self):
next(testIntermediate)
self.assertEqual(list(op(testIntermediate)), list(result2))


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

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

@pickle_deprecated
def test_cycle_copy_pickle(self):
# check copy, deepcopy, pickle
c = cycle('abc')
Expand Down Expand Up @@ -711,6 +739,7 @@ def test_cycle_copy_pickle(self):
d = pickle.loads(p) # rebuild the cycle object
self.assertEqual(take(20, d), list('cdeabcdeabcdeabcdeab'))

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

@pickle_deprecated
def test_cycle_setstate(self):
# Verify both modes for restoring state

Expand Down Expand Up @@ -778,6 +808,7 @@ def test_cycle_setstate(self):
self.assertRaises(TypeError, cycle('').__setstate__, ())
self.assertRaises(TypeError, cycle('').__setstate__, ([],))

@pickle_deprecated
def test_groupby(self):
# Check whether it accepts arguments correctly
self.assertEqual([], list(groupby([])))
Expand Down Expand Up @@ -935,6 +966,7 @@ def test_filter(self):
c = filter(isEven, range(6))
self.pickletest(proto, c)

@pickle_deprecated
def test_filterfalse(self):
self.assertEqual(list(filterfalse(isEven, range(6))), [1,3,5])
self.assertEqual(list(filterfalse(None, [0,1,0,2,0])), [0,0,0])
Expand Down Expand Up @@ -965,6 +997,7 @@ def test_zip(self):
lzip('abc', 'def'))

@support.impl_detail("tuple reuse is specific to CPython")
@pickle_deprecated
def test_zip_tuple_reuse(self):
ids = list(map(id, zip('abc', 'def')))
self.assertEqual(min(ids), max(ids))
Expand Down Expand Up @@ -1040,6 +1073,7 @@ def test_zip_longest_tuple_reuse(self):
ids = list(map(id, list(zip_longest('abc', 'def'))))
self.assertEqual(len(dict.fromkeys(ids)), len(ids))

@pickle_deprecated
def test_zip_longest_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
self.pickletest(proto, zip_longest("abc", "def"))
Expand Down Expand Up @@ -1186,6 +1220,7 @@ def test_product_tuple_reuse(self):
self.assertEqual(len(set(map(id, product('abc', 'def')))), 1)
self.assertNotEqual(len(set(map(id, list(product('abc', 'def'))))), 1)

@pickle_deprecated
def test_product_pickling(self):
# check copy, deepcopy, pickle
for args, result in [
Expand All @@ -1201,6 +1236,7 @@ def test_product_pickling(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
self.pickletest(proto, product(*args))

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

@pickle_deprecated
def test_repeat(self):
self.assertEqual(list(repeat(object='a', times=3)), ['a', 'a', 'a'])
self.assertEqual(lzip(range(3),repeat('a')),
Expand Down Expand Up @@ -1243,6 +1280,7 @@ def test_repeat_with_negative_times(self):
self.assertEqual(repr(repeat('a', times=-1)), "repeat('a', 0)")
self.assertEqual(repr(repeat('a', times=-2)), "repeat('a', 0)")

@pickle_deprecated
def test_map(self):
self.assertEqual(list(map(operator.pow, range(3), range(1,7))),
[0**1, 1**2, 2**3])
Expand Down Expand Up @@ -1273,6 +1311,7 @@ def test_map(self):
c = map(tupleize, 'abc', count())
self.pickletest(proto, c)

@pickle_deprecated
def test_starmap(self):
self.assertEqual(list(starmap(operator.pow, zip(range(3), range(1,7)))),
[0**1, 1**2, 2**3])
Expand Down Expand Up @@ -1300,6 +1339,7 @@ def test_starmap(self):
c = starmap(operator.pow, zip(range(3), range(1,7)))
self.pickletest(proto, c)

@pickle_deprecated
def test_islice(self):
for args in [ # islice(args) should agree with range(args)
(10, 20, 3),
Expand Down Expand Up @@ -1394,6 +1434,7 @@ def __index__(self):
self.assertEqual(list(islice(range(100), IntLike(10), IntLike(50), IntLike(5))),
list(range(10,50,5)))

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

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

@pickle_deprecated
def test_tee(self):
n = 200

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

@pickle_deprecated
def test_accumulate_reducible(self):
# check copy, deepcopy, pickle
data = [1, 2, 3, 4, 5]
Expand All @@ -1747,6 +1791,7 @@ def test_accumulate_reducible(self):
self.assertEqual(list(copy.deepcopy(it)), accumulated[1:])
self.assertEqual(list(copy.copy(it)), accumulated[1:])

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