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