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