1
1
"""
2
- Support pre-0.12 series pickle compatibility.
2
+ Pickle compatibility to pandas version 1.0
3
3
"""
4
4
from __future__ import annotations
5
5
6
6
import contextlib
7
- import copy
8
7
import io
9
- import pickle as pkl
8
+ import pickle
10
9
from typing import (
11
10
TYPE_CHECKING ,
12
11
Any ,
17
16
from pandas ._libs .arrays import NDArrayBacked
18
17
from pandas ._libs .tslibs import BaseOffset
19
18
20
- from pandas import Index
21
19
from pandas .core .arrays import (
22
20
DatetimeArray ,
23
21
PeriodArray ,
29
27
from collections .abc import Generator
30
28
31
29
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
-
66
30
# If classes are moved, provide compat here.
67
31
_class_locations_map = {
68
- ("pandas.core.sparse.array" , "SparseArray" ): ("pandas.core.arrays" , "SparseArray" ),
69
- # 15477
70
- ("pandas.core.base" , "FrozenNDArray" ): ("numpy" , "ndarray" ),
71
32
# Re-routing unpickle block logic to go through _unpickle_block instead
72
33
# for pandas <= 1.3.5
73
34
("pandas.core.internals.blocks" , "new_block" ): (
74
35
"pandas._libs.internals" ,
75
36
"_unpickle_block" ,
76
37
),
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" ): (
97
40
"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" ,
120
42
),
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
137
44
("pandas.core.indexes.numeric" , "Int64Index" ): (
138
45
"pandas.core.indexes.base" ,
139
46
"Index" ,
@@ -155,85 +62,55 @@ def load_reduce(self) -> None:
155
62
156
63
# our Unpickler sub-class to override methods and some dispatcher
157
64
# 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 ):
161
66
def find_class (self , module , name ):
162
- # override superclass
163
67
key = (module , name )
164
68
module , name = _class_locations_map .get (key , key )
165
69
return super ().find_class (module , name )
166
70
71
+ dispatch = pickle ._Unpickler .dispatch .copy ()
167
72
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 ]
208
77
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 )
229
109
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]
233
112
234
- return up .load ()
235
- except (ValueError , TypeError ):
236
- raise
113
+ dispatch [pickle .NEWOBJ [0 ]] = load_newobj # type: ignore[assignment]
237
114
238
115
239
116
def loads (
@@ -257,9 +134,9 @@ def patch_pickle() -> Generator[None, None, None]:
257
134
"""
258
135
Temporarily patch pickle to use our unpickler.
259
136
"""
260
- orig_loads = pkl .loads
137
+ orig_loads = pickle .loads
261
138
try :
262
- setattr (pkl , "loads" , loads )
139
+ setattr (pickle , "loads" , loads )
263
140
yield
264
141
finally :
265
- setattr (pkl , "loads" , orig_loads )
142
+ setattr (pickle , "loads" , orig_loads )
0 commit comments