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