@@ -158,6 +158,83 @@ def create_dynamic_class(name, bases):
158158 return result
159159
160160
161+ if _testbuffer is not None :
162+
163+ class PicklableNDArray :
164+ # A not-really-zero-copy picklable ndarray, as the ndarray()
165+ # constructor doesn't allow for it
166+
167+ zero_copy_reconstruct = False
168+
169+ def __init__ (self , * args , ** kwargs ):
170+ self .array = _testbuffer .ndarray (* args , ** kwargs )
171+
172+ def __getitem__ (self , idx ):
173+ cls = type (self )
174+ new = cls .__new__ (cls )
175+ new .array = self .array [idx ]
176+ return new
177+
178+ @property
179+ def readonly (self ):
180+ return self .array .readonly
181+
182+ @property
183+ def c_contiguous (self ):
184+ return self .array .c_contiguous
185+
186+ @property
187+ def f_contiguous (self ):
188+ return self .array .f_contiguous
189+
190+ def __eq__ (self , other ):
191+ if not isinstance (other , PicklableNDArray ):
192+ return NotImplemented
193+ return (other .array .format == self .array .format and
194+ other .array .shape == self .array .shape and
195+ other .array .strides == self .array .strides and
196+ other .array .readonly == self .array .readonly and
197+ other .array .tobytes () == self .array .tobytes ())
198+
199+ def __ne__ (self , other ):
200+ if not isinstance (other , PicklableNDArray ):
201+ return NotImplemented
202+ return not (self == other )
203+
204+ def __repr__ (self ):
205+ return ("{name}(shape={array.shape},"
206+ "strides={array.strides}, "
207+ "bytes={array.tobytes()})" ).format (
208+ name = type (self ).__name__ , array = self .array .shape )
209+
210+ def __reduce_ex__ (self , protocol ):
211+ if not self .array .contiguous :
212+ raise NotImplementedError ("Reconstructing a non-contiguous "
213+ "ndarray does not seem possible" )
214+ ndarray_kwargs = {"shape" : self .array .shape ,
215+ "strides" : self .array .strides ,
216+ "format" : self .array .format ,
217+ "flags" : (0 if self .readonly
218+ else _testbuffer .ND_WRITABLE )}
219+ pb = pickle .PickleBuffer (self .array )
220+ if protocol >= 5 :
221+ return (type (self )._reconstruct ,
222+ (pb , ndarray_kwargs ))
223+ else :
224+ # Need to serialize the bytes in physical order
225+ with pb .raw () as m :
226+ return (type (self )._reconstruct ,
227+ (m .tobytes (), ndarray_kwargs ))
228+
229+ @classmethod
230+ def _reconstruct (cls , obj , kwargs ):
231+ with memoryview (obj ) as m :
232+ # For some reason, ndarray() wants a list of integers...
233+ # XXX This only works if format == 'B'
234+ items = list (m .tobytes ())
235+ return cls (items , ** kwargs )
236+
237+
161238# DATA0 .. DATA4 are the pickles we expect under the various protocols, for
162239# the object returned by create_data().
163240
@@ -3082,6 +3159,8 @@ def test_proto(self):
30823159 self .assertEqual (count_opcode (pickle .PROTO , pickled ), 0 )
30833160
30843161 def test_bad_proto (self ):
3162+ if self .py_version < (3 , 8 ):
3163+ self .skipTest ('No protocol validation in this version' )
30853164 oob = protocols [- 1 ] + 1 # a future protocol
30863165 build_none = pickle .NONE + pickle .STOP
30873166 badpickle = pickle .PROTO + bytes ([oob ]) + build_none
@@ -3363,7 +3442,10 @@ def test_simple_newobj(self):
33633442 with self .subTest (proto = proto ):
33643443 s = self .dumps (x , proto )
33653444 if proto < 1 :
3366- self .assertIn (b'\n I64206' , s ) # INT
3445+ if self .py_version >= (3 , 7 ):
3446+ self .assertIn (b'\n I64206' , s ) # INT
3447+ else : # for test_xpickle
3448+ self .assertIn (b'64206' , s ) # INT or LONG
33673449 else :
33683450 self .assertIn (b'M\xce \xfa ' , s ) # BININT2
33693451 self .assertEqual (opcode_in_pickle (pickle .NEWOBJ , s ),
@@ -3379,7 +3461,10 @@ def test_complex_newobj(self):
33793461 with self .subTest (proto = proto ):
33803462 s = self .dumps (x , proto )
33813463 if proto < 1 :
3382- self .assertIn (b'\n I64206' , s ) # INT
3464+ if self .py_version >= (3 , 7 ):
3465+ self .assertIn (b'\n I64206' , s ) # INT
3466+ else : # for test_xpickle
3467+ self .assertIn (b'64206' , s ) # INT or LONG
33833468 elif proto < 2 :
33843469 self .assertIn (b'M\xce \xfa ' , s ) # BININT2
33853470 elif proto < 4 :
@@ -3395,11 +3480,14 @@ def test_complex_newobj(self):
33953480 def test_complex_newobj_ex (self ):
33963481 x = ComplexNewObjEx .__new__ (ComplexNewObjEx , 0xface ) # avoid __init__
33973482 x .abc = 666
3398- for proto in protocols :
3483+ for proto in protocols if self . py_version >= ( 3 , 6 ) else protocols [ 4 :] :
33993484 with self .subTest (proto = proto ):
34003485 s = self .dumps (x , proto )
34013486 if proto < 1 :
3402- self .assertIn (b'\n I64206' , s ) # INT
3487+ if self .py_version >= (3 , 7 ):
3488+ self .assertIn (b'\n I64206' , s ) # INT
3489+ else : # for test_xpickle
3490+ self .assertIn (b'64206' , s ) # INT or LONG
34033491 elif proto < 2 :
34043492 self .assertIn (b'M\xce \xfa ' , s ) # BININT2
34053493 elif proto < 4 :
@@ -3648,11 +3736,12 @@ def test_framing_large_objects(self):
36483736 [len (x ) for x in unpickled ])
36493737 # Perform full equality check if the lengths match.
36503738 self .assertEqual (obj , unpickled )
3651- n_frames = count_opcode (pickle .FRAME , pickled )
3652- # A single frame for small objects between
3653- # first two large objects.
3654- self .assertEqual (n_frames , 1 )
3655- self .check_frame_opcodes (pickled )
3739+ if self .py_version >= (3 , 7 ):
3740+ n_frames = count_opcode (pickle .FRAME , pickled )
3741+ # A single frame for small objects between
3742+ # first two large objects.
3743+ self .assertEqual (n_frames , 1 )
3744+ self .check_frame_opcodes (pickled )
36563745
36573746 def test_optional_frames (self ):
36583747 if pickle .HIGHEST_PROTOCOL < 4 :
0 commit comments