##// END OF EJS Templates
handle simple closures in pickleutil...
MinRK -
Show More
@@ -0,0 +1,62 b''
1
2 import pickle
3
4 import nose.tools as nt
5 from IPython.utils import codeutil
6 from IPython.utils.pickleutil import can, uncan
7
8 def interactive(f):
9 f.__module__ = '__main__'
10 return f
11
12 def dumps(obj):
13 return pickle.dumps(can(obj))
14
15 def loads(obj):
16 return uncan(pickle.loads(obj))
17
18 def test_no_closure():
19 @interactive
20 def foo():
21 a = 5
22 return a
23
24 pfoo = dumps(foo)
25 bar = loads(pfoo)
26 nt.assert_equal(foo(), bar())
27
28 def test_generator_closure():
29 # this only creates a closure on Python 3
30 @interactive
31 def foo():
32 i = 'i'
33 r = [ i for j in (1,2) ]
34 return r
35
36 pfoo = dumps(foo)
37 bar = loads(pfoo)
38 nt.assert_equal(foo(), bar())
39
40 def test_nested_closure():
41 @interactive
42 def foo():
43 i = 'i'
44 def g():
45 return i
46 return g()
47
48 pfoo = dumps(foo)
49 bar = loads(pfoo)
50 nt.assert_equal(foo(), bar())
51
52 def test_closure():
53 i = 'i'
54 @interactive
55 def foo():
56 return i
57
58 pfoo = dumps(foo)
59 bar = loads(pfoo)
60 nt.assert_equal(foo(), bar())
61
62 No newline at end of file
@@ -1,47 +1,35 b''
1 1 # encoding: utf-8
2 2
3 3 """Utilities to enable code objects to be pickled.
4 4
5 5 Any process that import this module will be able to pickle code objects. This
6 6 includes the func_code attribute of any function. Once unpickled, new
7 7 functions can be built using new.function(code, globals()). Eventually
8 8 we need to automate all of this so that functions themselves can be pickled.
9 9
10 10 Reference: A. Tremols, P Cogolo, "Python Cookbook," p 302-305
11 11 """
12 12
13 __docformat__ = "restructuredtext en"
14
15 #-------------------------------------------------------------------------------
16 # Copyright (C) 2008-2011 The IPython Development Team
17 #
18 # Distributed under the terms of the BSD License. The full license is in
19 # the file COPYING, distributed as part of this software.
20 #-------------------------------------------------------------------------------
21
22 #-------------------------------------------------------------------------------
23 # Imports
24 #-------------------------------------------------------------------------------
13 # Copyright (c) IPython Development Team.
14 # Distributed under the terms of the Modified BSD License.
25 15
26 16 import sys
27 17 import types
28 18 try:
29 19 import copyreg # Py 3
30 20 except ImportError:
31 21 import copy_reg as copyreg # Py 2
32 22
33 23 def code_ctor(*args):
34 24 return types.CodeType(*args)
35 25
36 26 def reduce_code(co):
37 if co.co_freevars or co.co_cellvars:
38 raise ValueError("Sorry, cannot pickle code objects with closures")
39 27 args = [co.co_argcount, co.co_nlocals, co.co_stacksize,
40 28 co.co_flags, co.co_code, co.co_consts, co.co_names,
41 29 co.co_varnames, co.co_filename, co.co_name, co.co_firstlineno,
42 co.co_lnotab]
30 co.co_lnotab, co.co_freevars, co.co_cellvars]
43 31 if sys.version_info[0] >= 3:
44 32 args.insert(1, co.co_kwonlyargcount)
45 33 return code_ctor, tuple(args)
46 34
47 35 copyreg.pickle(types.CodeType, reduce_code) No newline at end of file
@@ -1,390 +1,413 b''
1 1 # encoding: utf-8
2
3 2 """Pickle related utilities. Perhaps this should be called 'can'."""
4 3
5 __docformat__ = "restructuredtext en"
6
7 #-------------------------------------------------------------------------------
8 # Copyright (C) 2008-2011 The IPython Development Team
9 #
10 # Distributed under the terms of the BSD License. The full license is in
11 # the file COPYING, distributed as part of this software.
12 #-------------------------------------------------------------------------------
13
14 #-------------------------------------------------------------------------------
15 # Imports
16 #-------------------------------------------------------------------------------
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
17 6
18 7 import copy
19 8 import logging
20 9 import sys
21 10 from types import FunctionType
22 11
23 12 try:
24 13 import cPickle as pickle
25 14 except ImportError:
26 15 import pickle
27 16
28 17 from . import codeutil # This registers a hook when it's imported
29 18 from . import py3compat
30 19 from .importstring import import_item
31 20 from .py3compat import string_types, iteritems
32 21
33 22 from IPython.config import Application
34 23
35 24 if py3compat.PY3:
36 25 buffer = memoryview
37 26 class_type = type
38 27 else:
39 28 from types import ClassType
40 29 class_type = (type, ClassType)
41 30
31 def _get_cell_type(a=None):
32 """the type of a closure cell doesn't seem to be importable,
33 so just create one
34 """
35 def inner():
36 return a
37 return type(py3compat.get_closure(inner)[0])
38
39 cell_type = _get_cell_type()
40
42 41 #-------------------------------------------------------------------------------
43 42 # Functions
44 43 #-------------------------------------------------------------------------------
45 44
46 45
47 46 def use_dill():
48 47 """use dill to expand serialization support
49 48
50 49 adds support for object methods and closures to serialization.
51 50 """
52 51 # import dill causes most of the magic
53 52 import dill
54 53
55 54 # dill doesn't work with cPickle,
56 55 # tell the two relevant modules to use plain pickle
57 56
58 57 global pickle
59 58 pickle = dill
60 59
61 60 try:
62 61 from IPython.kernel.zmq import serialize
63 62 except ImportError:
64 63 pass
65 64 else:
66 65 serialize.pickle = dill
67 66
68 67 # disable special function handling, let dill take care of it
69 68 can_map.pop(FunctionType, None)
70 69
71 70
72 71 #-------------------------------------------------------------------------------
73 72 # Classes
74 73 #-------------------------------------------------------------------------------
75 74
76 75
77 76 class CannedObject(object):
78 77 def __init__(self, obj, keys=[], hook=None):
79 78 """can an object for safe pickling
80 79
81 80 Parameters
82 81 ==========
83 82
84 83 obj:
85 84 The object to be canned
86 85 keys: list (optional)
87 86 list of attribute names that will be explicitly canned / uncanned
88 87 hook: callable (optional)
89 88 An optional extra callable,
90 89 which can do additional processing of the uncanned object.
91 90
92 91 large data may be offloaded into the buffers list,
93 92 used for zero-copy transfers.
94 93 """
95 94 self.keys = keys
96 95 self.obj = copy.copy(obj)
97 96 self.hook = can(hook)
98 97 for key in keys:
99 98 setattr(self.obj, key, can(getattr(obj, key)))
100 99
101 100 self.buffers = []
102 101
103 102 def get_object(self, g=None):
104 103 if g is None:
105 104 g = {}
106 105 obj = self.obj
107 106 for key in self.keys:
108 107 setattr(obj, key, uncan(getattr(obj, key), g))
109 108
110 109 if self.hook:
111 110 self.hook = uncan(self.hook, g)
112 111 self.hook(obj, g)
113 112 return self.obj
114 113
115 114
116 115 class Reference(CannedObject):
117 116 """object for wrapping a remote reference by name."""
118 117 def __init__(self, name):
119 118 if not isinstance(name, string_types):
120 119 raise TypeError("illegal name: %r"%name)
121 120 self.name = name
122 121 self.buffers = []
123 122
124 123 def __repr__(self):
125 124 return "<Reference: %r>"%self.name
126 125
127 126 def get_object(self, g=None):
128 127 if g is None:
129 128 g = {}
130 129
131 130 return eval(self.name, g)
132 131
133 132
133 class CannedCell(CannedObject):
134 """Can a closure cell"""
135 def __init__(self, cell):
136 self.cell_contents = can(cell.cell_contents)
137
138 def get_object(self, g=None):
139 cell_contents = uncan(self.cell_contents, g)
140 def inner():
141 return cell_contents
142 return py3compat.get_closure(inner)[0]
143
144
134 145 class CannedFunction(CannedObject):
135 146
136 147 def __init__(self, f):
137 148 self._check_type(f)
138 149 self.code = f.__code__
139 150 if f.__defaults__:
140 151 self.defaults = [ can(fd) for fd in f.__defaults__ ]
141 152 else:
142 153 self.defaults = None
154
155 closure = py3compat.get_closure(f)
156 if closure:
157 self.closure = tuple( can(cell) for cell in closure )
158 else:
159 self.closure = None
160
143 161 self.module = f.__module__ or '__main__'
144 162 self.__name__ = f.__name__
145 163 self.buffers = []
146 164
147 165 def _check_type(self, obj):
148 166 assert isinstance(obj, FunctionType), "Not a function type"
149 167
150 168 def get_object(self, g=None):
151 169 # try to load function back into its module:
152 170 if not self.module.startswith('__'):
153 171 __import__(self.module)
154 172 g = sys.modules[self.module].__dict__
155 173
156 174 if g is None:
157 175 g = {}
158 176 if self.defaults:
159 177 defaults = tuple(uncan(cfd, g) for cfd in self.defaults)
160 178 else:
161 179 defaults = None
162 newFunc = FunctionType(self.code, g, self.__name__, defaults)
180 if self.closure:
181 closure = tuple(uncan(cell, g) for cell in self.closure)
182 else:
183 closure = None
184 newFunc = FunctionType(self.code, g, self.__name__, defaults, closure)
163 185 return newFunc
164 186
165 187 class CannedClass(CannedObject):
166 188
167 189 def __init__(self, cls):
168 190 self._check_type(cls)
169 191 self.name = cls.__name__
170 192 self.old_style = not isinstance(cls, type)
171 193 self._canned_dict = {}
172 194 for k,v in cls.__dict__.items():
173 195 if k not in ('__weakref__', '__dict__'):
174 196 self._canned_dict[k] = can(v)
175 197 if self.old_style:
176 198 mro = []
177 199 else:
178 200 mro = cls.mro()
179 201
180 202 self.parents = [ can(c) for c in mro[1:] ]
181 203 self.buffers = []
182 204
183 205 def _check_type(self, obj):
184 206 assert isinstance(obj, class_type), "Not a class type"
185 207
186 208 def get_object(self, g=None):
187 209 parents = tuple(uncan(p, g) for p in self.parents)
188 210 return type(self.name, parents, uncan_dict(self._canned_dict, g=g))
189 211
190 212 class CannedArray(CannedObject):
191 213 def __init__(self, obj):
192 214 from numpy import ascontiguousarray
193 215 self.shape = obj.shape
194 216 self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str
195 217 self.pickled = False
196 218 if sum(obj.shape) == 0:
197 219 self.pickled = True
198 220 elif obj.dtype == 'O':
199 221 # can't handle object dtype with buffer approach
200 222 self.pickled = True
201 223 elif obj.dtype.fields and any(dt == 'O' for dt,sz in obj.dtype.fields.values()):
202 224 self.pickled = True
203 225 if self.pickled:
204 226 # just pickle it
205 227 self.buffers = [pickle.dumps(obj, -1)]
206 228 else:
207 229 # ensure contiguous
208 230 obj = ascontiguousarray(obj, dtype=None)
209 231 self.buffers = [buffer(obj)]
210 232
211 233 def get_object(self, g=None):
212 234 from numpy import frombuffer
213 235 data = self.buffers[0]
214 236 if self.pickled:
215 237 # no shape, we just pickled it
216 238 return pickle.loads(data)
217 239 else:
218 240 return frombuffer(data, dtype=self.dtype).reshape(self.shape)
219 241
220 242
221 243 class CannedBytes(CannedObject):
222 244 wrap = bytes
223 245 def __init__(self, obj):
224 246 self.buffers = [obj]
225 247
226 248 def get_object(self, g=None):
227 249 data = self.buffers[0]
228 250 return self.wrap(data)
229 251
230 252 def CannedBuffer(CannedBytes):
231 253 wrap = buffer
232 254
233 255 #-------------------------------------------------------------------------------
234 256 # Functions
235 257 #-------------------------------------------------------------------------------
236 258
237 259 def _logger():
238 260 """get the logger for the current Application
239 261
240 262 the root logger will be used if no Application is running
241 263 """
242 264 if Application.initialized():
243 265 logger = Application.instance().log
244 266 else:
245 267 logger = logging.getLogger()
246 268 if not logger.handlers:
247 269 logging.basicConfig()
248 270
249 271 return logger
250 272
251 273 def _import_mapping(mapping, original=None):
252 274 """import any string-keys in a type mapping
253 275
254 276 """
255 277 log = _logger()
256 278 log.debug("Importing canning map")
257 279 for key,value in list(mapping.items()):
258 280 if isinstance(key, string_types):
259 281 try:
260 282 cls = import_item(key)
261 283 except Exception:
262 284 if original and key not in original:
263 285 # only message on user-added classes
264 286 log.error("canning class not importable: %r", key, exc_info=True)
265 287 mapping.pop(key)
266 288 else:
267 289 mapping[cls] = mapping.pop(key)
268 290
269 291 def istype(obj, check):
270 292 """like isinstance(obj, check), but strict
271 293
272 294 This won't catch subclasses.
273 295 """
274 296 if isinstance(check, tuple):
275 297 for cls in check:
276 298 if type(obj) is cls:
277 299 return True
278 300 return False
279 301 else:
280 302 return type(obj) is check
281 303
282 304 def can(obj):
283 305 """prepare an object for pickling"""
284 306
285 307 import_needed = False
286 308
287 309 for cls,canner in iteritems(can_map):
288 310 if isinstance(cls, string_types):
289 311 import_needed = True
290 312 break
291 313 elif istype(obj, cls):
292 314 return canner(obj)
293 315
294 316 if import_needed:
295 317 # perform can_map imports, then try again
296 318 # this will usually only happen once
297 319 _import_mapping(can_map, _original_can_map)
298 320 return can(obj)
299 321
300 322 return obj
301 323
302 324 def can_class(obj):
303 325 if isinstance(obj, class_type) and obj.__module__ == '__main__':
304 326 return CannedClass(obj)
305 327 else:
306 328 return obj
307 329
308 330 def can_dict(obj):
309 331 """can the *values* of a dict"""
310 332 if istype(obj, dict):
311 333 newobj = {}
312 334 for k, v in iteritems(obj):
313 335 newobj[k] = can(v)
314 336 return newobj
315 337 else:
316 338 return obj
317 339
318 340 sequence_types = (list, tuple, set)
319 341
320 342 def can_sequence(obj):
321 343 """can the elements of a sequence"""
322 344 if istype(obj, sequence_types):
323 345 t = type(obj)
324 346 return t([can(i) for i in obj])
325 347 else:
326 348 return obj
327 349
328 350 def uncan(obj, g=None):
329 351 """invert canning"""
330 352
331 353 import_needed = False
332 354 for cls,uncanner in iteritems(uncan_map):
333 355 if isinstance(cls, string_types):
334 356 import_needed = True
335 357 break
336 358 elif isinstance(obj, cls):
337 359 return uncanner(obj, g)
338 360
339 361 if import_needed:
340 362 # perform uncan_map imports, then try again
341 363 # this will usually only happen once
342 364 _import_mapping(uncan_map, _original_uncan_map)
343 365 return uncan(obj, g)
344 366
345 367 return obj
346 368
347 369 def uncan_dict(obj, g=None):
348 370 if istype(obj, dict):
349 371 newobj = {}
350 372 for k, v in iteritems(obj):
351 373 newobj[k] = uncan(v,g)
352 374 return newobj
353 375 else:
354 376 return obj
355 377
356 378 def uncan_sequence(obj, g=None):
357 379 if istype(obj, sequence_types):
358 380 t = type(obj)
359 381 return t([uncan(i,g) for i in obj])
360 382 else:
361 383 return obj
362 384
363 385 def _uncan_dependent_hook(dep, g=None):
364 386 dep.check_dependency()
365 387
366 388 def can_dependent(obj):
367 389 return CannedObject(obj, keys=('f', 'df'), hook=_uncan_dependent_hook)
368 390
369 391 #-------------------------------------------------------------------------------
370 392 # API dictionaries
371 393 #-------------------------------------------------------------------------------
372 394
373 395 # These dicts can be extended for custom serialization of new objects
374 396
375 397 can_map = {
376 398 'IPython.parallel.dependent' : can_dependent,
377 399 'numpy.ndarray' : CannedArray,
378 400 FunctionType : CannedFunction,
379 401 bytes : CannedBytes,
380 402 buffer : CannedBuffer,
403 cell_type : CannedCell,
381 404 class_type : can_class,
382 405 }
383 406
384 407 uncan_map = {
385 408 CannedObject : lambda obj, g: obj.get_object(g),
386 409 }
387 410
388 411 # for use in _import_mapping:
389 412 _original_can_map = can_map.copy()
390 413 _original_uncan_map = uncan_map.copy()
General Comments 0
You need to be logged in to leave comments. Login now