Skip to content

Commit

Permalink
GH-101588: Deprecate pickle/copy/deepcopy support in itertools (GH-10…
Browse files Browse the repository at this point in the history
  • Loading branch information
rhettinger committed May 26, 2023
1 parent 328422c commit 402ee5a
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 3 deletions.
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

0 comments on commit 402ee5a

Please sign in to comment.