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