Skip to content

Commit ada009a

Browse files
authored
CLN: Remove pickle support pre-pandas 1.0 (#57555)
* Centeralize methods to class * Cleanups * CLN: Remove pickle support pre-pandas 1.0 * Typing * clean:
1 parent 4ed67ac commit ada009a

File tree

4 files changed

+57
-180
lines changed

4 files changed

+57
-180
lines changed

doc/source/whatsnew/v3.0.0.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ Other API changes
9494
- Made ``dtype`` a required argument in :meth:`ExtensionArray._from_sequence_of_strings` (:issue:`56519`)
9595
- Updated :meth:`DataFrame.to_excel` so that the output spreadsheet has no styling. Custom styling can still be done using :meth:`Styler.to_excel` (:issue:`54154`)
9696
- pickle and HDF (``.h5``) files created with Python 2 are no longer explicitly supported (:issue:`57387`)
97-
-
97+
- pickled objects from pandas version less than ``1.0.0`` are no longer supported (:issue:`57155`)
9898

9999
.. ---------------------------------------------------------------------------
100100
.. _whatsnew_300.deprecations:

pandas/_libs/tslibs/nattype.pyx

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ cdef _nat_rdivide_op(self, other):
7676
return NotImplemented
7777

7878

79-
def __nat_unpickle(*args):
79+
def _nat_unpickle(*args):
8080
# return constant defined in the module
8181
return c_NaT
8282

@@ -360,7 +360,7 @@ class NaTType(_NaT):
360360
return self.__reduce__()
361361

362362
def __reduce__(self):
363-
return (__nat_unpickle, (None, ))
363+
return (_nat_unpickle, (None, ))
364364

365365
def __rtruediv__(self, other):
366366
return _nat_rdivide_op(self, other)

pandas/compat/pickle_compat.py

+49-172
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
"""
2-
Support pre-0.12 series pickle compatibility.
2+
Pickle compatibility to pandas version 1.0
33
"""
44
from __future__ import annotations
55

66
import contextlib
7-
import copy
87
import io
9-
import pickle as pkl
8+
import pickle
109
from typing import (
1110
TYPE_CHECKING,
1211
Any,
@@ -17,7 +16,6 @@
1716
from pandas._libs.arrays import NDArrayBacked
1817
from pandas._libs.tslibs import BaseOffset
1918

20-
from pandas import Index
2119
from pandas.core.arrays import (
2220
DatetimeArray,
2321
PeriodArray,
@@ -29,111 +27,20 @@
2927
from collections.abc import Generator
3028

3129

32-
def load_reduce(self) -> None:
33-
stack = self.stack
34-
args = stack.pop()
35-
func = stack[-1]
36-
37-
try:
38-
stack[-1] = func(*args)
39-
return
40-
except TypeError as err:
41-
# If we have a deprecated function,
42-
# try to replace and try again.
43-
44-
msg = "_reconstruct: First argument must be a sub-type of ndarray"
45-
46-
if msg in str(err):
47-
try:
48-
cls = args[0]
49-
stack[-1] = object.__new__(cls)
50-
return
51-
except TypeError:
52-
pass
53-
elif args and isinstance(args[0], type) and issubclass(args[0], BaseOffset):
54-
# TypeError: object.__new__(Day) is not safe, use Day.__new__()
55-
cls = args[0]
56-
stack[-1] = cls.__new__(*args)
57-
return
58-
elif args and issubclass(args[0], PeriodArray):
59-
cls = args[0]
60-
stack[-1] = NDArrayBacked.__new__(*args)
61-
return
62-
63-
raise
64-
65-
6630
# If classes are moved, provide compat here.
6731
_class_locations_map = {
68-
("pandas.core.sparse.array", "SparseArray"): ("pandas.core.arrays", "SparseArray"),
69-
# 15477
70-
("pandas.core.base", "FrozenNDArray"): ("numpy", "ndarray"),
7132
# Re-routing unpickle block logic to go through _unpickle_block instead
7233
# for pandas <= 1.3.5
7334
("pandas.core.internals.blocks", "new_block"): (
7435
"pandas._libs.internals",
7536
"_unpickle_block",
7637
),
77-
("pandas.core.indexes.frozen", "FrozenNDArray"): ("numpy", "ndarray"),
78-
("pandas.core.base", "FrozenList"): ("pandas.core.indexes.frozen", "FrozenList"),
79-
# 10890
80-
("pandas.core.series", "TimeSeries"): ("pandas.core.series", "Series"),
81-
("pandas.sparse.series", "SparseTimeSeries"): (
82-
"pandas.core.sparse.series",
83-
"SparseSeries",
84-
),
85-
# 12588, extensions moving
86-
("pandas._sparse", "BlockIndex"): ("pandas._libs.sparse", "BlockIndex"),
87-
("pandas.tslib", "Timestamp"): ("pandas._libs.tslib", "Timestamp"),
88-
# 18543 moving period
89-
("pandas._period", "Period"): ("pandas._libs.tslibs.period", "Period"),
90-
("pandas._libs.period", "Period"): ("pandas._libs.tslibs.period", "Period"),
91-
# 18014 moved __nat_unpickle from _libs.tslib-->_libs.tslibs.nattype
92-
("pandas.tslib", "__nat_unpickle"): (
93-
"pandas._libs.tslibs.nattype",
94-
"__nat_unpickle",
95-
),
96-
("pandas._libs.tslib", "__nat_unpickle"): (
38+
# Avoid Cython's warning "contradiction to to Python 'class private name' rules"
39+
("pandas._libs.tslibs.nattype", "__nat_unpickle"): (
9740
"pandas._libs.tslibs.nattype",
98-
"__nat_unpickle",
99-
),
100-
# 15998 top-level dirs moving
101-
("pandas.sparse.array", "SparseArray"): (
102-
"pandas.core.arrays.sparse",
103-
"SparseArray",
104-
),
105-
("pandas.indexes.base", "_new_Index"): ("pandas.core.indexes.base", "_new_Index"),
106-
("pandas.indexes.base", "Index"): ("pandas.core.indexes.base", "Index"),
107-
("pandas.indexes.numeric", "Int64Index"): (
108-
"pandas.core.indexes.base",
109-
"Index", # updated in 50775
110-
),
111-
("pandas.indexes.range", "RangeIndex"): ("pandas.core.indexes.range", "RangeIndex"),
112-
("pandas.indexes.multi", "MultiIndex"): ("pandas.core.indexes.multi", "MultiIndex"),
113-
("pandas.tseries.index", "_new_DatetimeIndex"): (
114-
"pandas.core.indexes.datetimes",
115-
"_new_DatetimeIndex",
116-
),
117-
("pandas.tseries.index", "DatetimeIndex"): (
118-
"pandas.core.indexes.datetimes",
119-
"DatetimeIndex",
41+
"_nat_unpickle",
12042
),
121-
("pandas.tseries.period", "PeriodIndex"): (
122-
"pandas.core.indexes.period",
123-
"PeriodIndex",
124-
),
125-
# 19269, arrays moving
126-
("pandas.core.categorical", "Categorical"): ("pandas.core.arrays", "Categorical"),
127-
# 19939, add timedeltaindex, float64index compat from 15998 move
128-
("pandas.tseries.tdi", "TimedeltaIndex"): (
129-
"pandas.core.indexes.timedeltas",
130-
"TimedeltaIndex",
131-
),
132-
("pandas.indexes.numeric", "Float64Index"): (
133-
"pandas.core.indexes.base",
134-
"Index", # updated in 50775
135-
),
136-
# 50775, remove Int64Index, UInt64Index & Float64Index from codabase
43+
# 50775, remove Int64Index, UInt64Index & Float64Index from codebase
13744
("pandas.core.indexes.numeric", "Int64Index"): (
13845
"pandas.core.indexes.base",
13946
"Index",
@@ -155,85 +62,55 @@ def load_reduce(self) -> None:
15562

15663
# our Unpickler sub-class to override methods and some dispatcher
15764
# functions for compat and uses a non-public class of the pickle module.
158-
159-
160-
class Unpickler(pkl._Unpickler):
65+
class Unpickler(pickle._Unpickler):
16166
def find_class(self, module, name):
162-
# override superclass
16367
key = (module, name)
16468
module, name = _class_locations_map.get(key, key)
16569
return super().find_class(module, name)
16670

71+
dispatch = pickle._Unpickler.dispatch.copy()
16772

168-
Unpickler.dispatch = copy.copy(Unpickler.dispatch)
169-
Unpickler.dispatch[pkl.REDUCE[0]] = load_reduce
170-
171-
172-
def load_newobj(self) -> None:
173-
args = self.stack.pop()
174-
cls = self.stack[-1]
175-
176-
# compat
177-
if issubclass(cls, Index):
178-
obj = object.__new__(cls)
179-
elif issubclass(cls, DatetimeArray) and not args:
180-
arr = np.array([], dtype="M8[ns]")
181-
obj = cls.__new__(cls, arr, arr.dtype)
182-
elif issubclass(cls, TimedeltaArray) and not args:
183-
arr = np.array([], dtype="m8[ns]")
184-
obj = cls.__new__(cls, arr, arr.dtype)
185-
elif cls is BlockManager and not args:
186-
obj = cls.__new__(cls, (), [], False)
187-
else:
188-
obj = cls.__new__(cls, *args)
189-
190-
self.stack[-1] = obj
191-
192-
193-
Unpickler.dispatch[pkl.NEWOBJ[0]] = load_newobj
194-
195-
196-
def load_newobj_ex(self) -> None:
197-
kwargs = self.stack.pop()
198-
args = self.stack.pop()
199-
cls = self.stack.pop()
200-
201-
# compat
202-
if issubclass(cls, Index):
203-
obj = object.__new__(cls)
204-
else:
205-
obj = cls.__new__(cls, *args, **kwargs)
206-
self.append(obj)
207-
73+
def load_reduce(self) -> None:
74+
stack = self.stack # type: ignore[attr-defined]
75+
args = stack.pop()
76+
func = stack[-1]
20877

209-
try:
210-
Unpickler.dispatch[pkl.NEWOBJ_EX[0]] = load_newobj_ex
211-
except (AttributeError, KeyError):
212-
pass
213-
214-
215-
def load(fh, encoding: str | None = None, is_verbose: bool = False) -> Any:
216-
"""
217-
Load a pickle, with a provided encoding,
218-
219-
Parameters
220-
----------
221-
fh : a filelike object
222-
encoding : an optional encoding
223-
is_verbose : show exception output
224-
"""
225-
try:
226-
fh.seek(0)
227-
if encoding is not None:
228-
up = Unpickler(fh, encoding=encoding)
78+
try:
79+
stack[-1] = func(*args)
80+
except TypeError:
81+
# If we have a deprecated function,
82+
# try to replace and try again.
83+
if args and isinstance(args[0], type) and issubclass(args[0], BaseOffset):
84+
# TypeError: object.__new__(Day) is not safe, use Day.__new__()
85+
cls = args[0]
86+
stack[-1] = cls.__new__(*args)
87+
return
88+
elif args and issubclass(args[0], PeriodArray):
89+
cls = args[0]
90+
stack[-1] = NDArrayBacked.__new__(*args)
91+
return
92+
raise
93+
94+
dispatch[pickle.REDUCE[0]] = load_reduce # type: ignore[assignment]
95+
96+
def load_newobj(self) -> None:
97+
args = self.stack.pop() # type: ignore[attr-defined]
98+
cls = self.stack.pop() # type: ignore[attr-defined]
99+
100+
# compat
101+
if issubclass(cls, DatetimeArray) and not args:
102+
arr = np.array([], dtype="M8[ns]")
103+
obj = cls.__new__(cls, arr, arr.dtype)
104+
elif issubclass(cls, TimedeltaArray) and not args:
105+
arr = np.array([], dtype="m8[ns]")
106+
obj = cls.__new__(cls, arr, arr.dtype)
107+
elif cls is BlockManager and not args:
108+
obj = cls.__new__(cls, (), [], False)
229109
else:
230-
up = Unpickler(fh)
231-
# "Unpickler" has no attribute "is_verbose" [attr-defined]
232-
up.is_verbose = is_verbose # type: ignore[attr-defined]
110+
obj = cls.__new__(cls, *args)
111+
self.append(obj) # type: ignore[attr-defined]
233112

234-
return up.load()
235-
except (ValueError, TypeError):
236-
raise
113+
dispatch[pickle.NEWOBJ[0]] = load_newobj # type: ignore[assignment]
237114

238115

239116
def loads(
@@ -257,9 +134,9 @@ def patch_pickle() -> Generator[None, None, None]:
257134
"""
258135
Temporarily patch pickle to use our unpickler.
259136
"""
260-
orig_loads = pkl.loads
137+
orig_loads = pickle.loads
261138
try:
262-
setattr(pkl, "loads", loads)
139+
setattr(pickle, "loads", loads)
263140
yield
264141
finally:
265-
setattr(pkl, "loads", orig_loads)
142+
setattr(pickle, "loads", orig_loads)

pandas/io/pickle.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
)
99
import warnings
1010

11-
from pandas.compat import pickle_compat as pc
11+
from pandas.compat import pickle_compat
1212
from pandas.util._decorators import doc
1313

1414
from pandas.core.shared_docs import _shared_docs
@@ -158,7 +158,7 @@ def read_pickle(
158158
159159
Notes
160160
-----
161-
read_pickle is only guaranteed to be backwards compatible to pandas 0.20.3
161+
read_pickle is only guaranteed to be backwards compatible to pandas 1.0
162162
provided the object was serialized with to_pickle.
163163
164164
Examples
@@ -195,7 +195,6 @@ def read_pickle(
195195
) as handles:
196196
# 1) try standard library Pickle
197197
# 2) try pickle_compat (older pandas version) to handle subclass changes
198-
199198
try:
200199
with warnings.catch_warnings(record=True):
201200
# We want to silence any warnings about, e.g. moved modules.
@@ -204,5 +203,6 @@ def read_pickle(
204203
except excs_to_catch:
205204
# e.g.
206205
# "No module named 'pandas.core.sparse.series'"
207-
# "Can't get attribute '__nat_unpickle' on <module 'pandas._libs.tslib"
208-
return pc.load(handles.handle, encoding=None)
206+
# "Can't get attribute '_nat_unpickle' on <module 'pandas._libs.tslib"
207+
handles.handle.seek(0)
208+
return pickle_compat.Unpickler(handles.handle).load()

0 commit comments

Comments
 (0)