pickleutil.py
354 lines
| 9.6 KiB
| text/x-python
|
PythonLexer
MinRK
|
r3539 | # encoding: utf-8 | ||
"""Pickle related utilities. Perhaps this should be called 'can'.""" | ||||
__docformat__ = "restructuredtext en" | ||||
#------------------------------------------------------------------------------- | ||||
Matthias BUSSONNIER
|
r5390 | # Copyright (C) 2008-2011 The IPython Development Team | ||
MinRK
|
r3539 | # | ||
# Distributed under the terms of the BSD License. The full license is in | ||||
# the file COPYING, distributed as part of this software. | ||||
#------------------------------------------------------------------------------- | ||||
#------------------------------------------------------------------------------- | ||||
# Imports | ||||
#------------------------------------------------------------------------------- | ||||
MinRK
|
r3607 | import copy | ||
MinRK
|
r8034 | import logging | ||
MinRK
|
r3664 | import sys | ||
MinRK
|
r9002 | from types import FunctionType | ||
MinRK
|
r3607 | |||
MinRK
|
r7967 | try: | ||
import cPickle as pickle | ||||
except ImportError: | ||||
import pickle | ||||
try: | ||||
import numpy | ||||
except: | ||||
numpy = None | ||||
MinRK
|
r3557 | import codeutil | ||
MinRK
|
r7967 | import py3compat | ||
from importstring import import_item | ||||
MinRK
|
r8034 | from IPython.config import Application | ||
MinRK
|
r7967 | if py3compat.PY3: | ||
buffer = memoryview | ||||
MinRK
|
r9002 | class_type = type | ||
else: | ||||
from types import ClassType | ||||
class_type = (type, ClassType) | ||||
MinRK
|
r3539 | |||
MinRK
|
r3607 | #------------------------------------------------------------------------------- | ||
# Classes | ||||
#------------------------------------------------------------------------------- | ||||
MinRK
|
r3539 | class CannedObject(object): | ||
MinRK
|
r9985 | def __init__(self, obj, keys=[], hook=None): | ||
MinRK
|
r9987 | """can an object for safe pickling | ||
Parameters | ||||
========== | ||||
obj: | ||||
The object to be canned | ||||
keys: list (optional) | ||||
list of attribute names that will be explicitly canned / uncanned | ||||
hook: callable (optional) | ||||
An optional extra callable, | ||||
which can do additional processing of the uncanned object. | ||||
large data may be offloaded into the buffers list, | ||||
used for zero-copy transfers. | ||||
""" | ||||
MinRK
|
r3546 | self.keys = keys | ||
MinRK
|
r3607 | self.obj = copy.copy(obj) | ||
MinRK
|
r9985 | self.hook = can(hook) | ||
MinRK
|
r3546 | for key in keys: | ||
MinRK
|
r3607 | setattr(self.obj, key, can(getattr(obj, key))) | ||
MinRK
|
r7967 | |||
self.buffers = [] | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r7967 | def get_object(self, g=None): | ||
MinRK
|
r3546 | if g is None: | ||
MinRK
|
r7967 | g = {} | ||
MinRK
|
r9985 | obj = self.obj | ||
MinRK
|
r3546 | for key in self.keys: | ||
MinRK
|
r9985 | setattr(obj, key, uncan(getattr(obj, key), g)) | ||
MinRK
|
r9986 | |||
MinRK
|
r9985 | if self.hook: | ||
self.hook = uncan(self.hook, g) | ||||
self.hook(obj, g) | ||||
MinRK
|
r3546 | return self.obj | ||
MinRK
|
r7967 | |||
MinRK
|
r3546 | |||
MinRK
|
r3643 | class Reference(CannedObject): | ||
"""object for wrapping a remote reference by name.""" | ||||
def __init__(self, name): | ||||
if not isinstance(name, basestring): | ||||
raise TypeError("illegal name: %r"%name) | ||||
self.name = name | ||||
MinRK
|
r7967 | self.buffers = [] | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3643 | def __repr__(self): | ||
return "<Reference: %r>"%self.name | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r7967 | def get_object(self, g=None): | ||
MinRK
|
r3643 | if g is None: | ||
MinRK
|
r7967 | g = {} | ||
MinRK
|
r6159 | |||
return eval(self.name, g) | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3546 | |||
MinRK
|
r3539 | class CannedFunction(CannedObject): | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3539 | def __init__(self, f): | ||
MinRK
|
r7967 | self._check_type(f) | ||
MinRK
|
r3539 | self.code = f.func_code | ||
MinRK
|
r8041 | if f.func_defaults: | ||
self.defaults = [ can(fd) for fd in f.func_defaults ] | ||||
else: | ||||
self.defaults = None | ||||
MinRK
|
r3664 | self.module = f.__module__ or '__main__' | ||
MinRK
|
r3607 | self.__name__ = f.__name__ | ||
MinRK
|
r7967 | self.buffers = [] | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r7967 | def _check_type(self, obj): | ||
MinRK
|
r3539 | assert isinstance(obj, FunctionType), "Not a function type" | ||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r7967 | def get_object(self, g=None): | ||
MinRK
|
r3664 | # try to load function back into its module: | ||
if not self.module.startswith('__'): | ||||
MinRK
|
r8405 | __import__(self.module) | ||
g = sys.modules[self.module].__dict__ | ||||
Bernardo B. Marques
|
r4872 | |||
MinRK
|
r3539 | if g is None: | ||
MinRK
|
r7967 | g = {} | ||
MinRK
|
r8041 | if self.defaults: | ||
defaults = tuple(uncan(cfd, g) for cfd in self.defaults) | ||||
else: | ||||
defaults = None | ||||
newFunc = FunctionType(self.code, g, self.__name__, defaults) | ||||
MinRK
|
r3539 | return newFunc | ||
MinRK
|
r9000 | class CannedClass(CannedObject): | ||
def __init__(self, cls): | ||||
self._check_type(cls) | ||||
self.name = cls.__name__ | ||||
self.old_style = not isinstance(cls, type) | ||||
self._canned_dict = {} | ||||
for k,v in cls.__dict__.items(): | ||||
if k not in ('__weakref__', '__dict__'): | ||||
self._canned_dict[k] = can(v) | ||||
if self.old_style: | ||||
mro = [] | ||||
else: | ||||
mro = cls.mro() | ||||
self.parents = [ can(c) for c in mro[1:] ] | ||||
self.buffers = [] | ||||
def _check_type(self, obj): | ||||
MinRK
|
r9002 | assert isinstance(obj, class_type), "Not a class type" | ||
MinRK
|
r9000 | |||
def get_object(self, g=None): | ||||
parents = tuple(uncan(p, g) for p in self.parents) | ||||
return type(self.name, parents, uncan_dict(self._canned_dict, g=g)) | ||||
MinRK
|
r7967 | |||
class CannedArray(CannedObject): | ||||
def __init__(self, obj): | ||||
self.shape = obj.shape | ||||
MinRK
|
r7971 | self.dtype = obj.dtype.descr if obj.dtype.fields else obj.dtype.str | ||
MinRK
|
r7967 | if sum(obj.shape) == 0: | ||
# just pickle it | ||||
self.buffers = [pickle.dumps(obj, -1)] | ||||
else: | ||||
# ensure contiguous | ||||
obj = numpy.ascontiguousarray(obj, dtype=None) | ||||
self.buffers = [buffer(obj)] | ||||
def get_object(self, g=None): | ||||
data = self.buffers[0] | ||||
if sum(self.shape) == 0: | ||||
# no shape, we just pickled it | ||||
return pickle.loads(data) | ||||
else: | ||||
return numpy.frombuffer(data, dtype=self.dtype).reshape(self.shape) | ||||
class CannedBytes(CannedObject): | ||||
wrap = bytes | ||||
def __init__(self, obj): | ||||
self.buffers = [obj] | ||||
def get_object(self, g=None): | ||||
data = self.buffers[0] | ||||
return self.wrap(data) | ||||
def CannedBuffer(CannedBytes): | ||||
wrap = buffer | ||||
MinRK
|
r3607 | #------------------------------------------------------------------------------- | ||
# Functions | ||||
#------------------------------------------------------------------------------- | ||||
MinRK
|
r8081 | def _logger(): | ||
"""get the logger for the current Application | ||||
the root logger will be used if no Application is running | ||||
""" | ||||
MinRK
|
r8034 | if Application.initialized(): | ||
logger = Application.instance().log | ||||
else: | ||||
logger = logging.getLogger() | ||||
if not logger.handlers: | ||||
logging.basicConfig() | ||||
MinRK
|
r8081 | |||
return logger | ||||
def _import_mapping(mapping, original=None): | ||||
"""import any string-keys in a type mapping | ||||
""" | ||||
log = _logger() | ||||
log.debug("Importing canning map") | ||||
for key,value in mapping.items(): | ||||
if isinstance(key, basestring): | ||||
try: | ||||
cls = import_item(key) | ||||
except Exception: | ||||
if original and key not in original: | ||||
# only message on user-added classes | ||||
log.error("cannning class not importable: %r", key, exc_info=True) | ||||
mapping.pop(key) | ||||
else: | ||||
mapping[cls] = mapping.pop(key) | ||||
MinRK
|
r3539 | |||
MinRK
|
r9138 | def istype(obj, check): | ||
"""like isinstance(obj, check), but strict | ||||
This won't catch subclasses. | ||||
""" | ||||
if isinstance(check, tuple): | ||||
for cls in check: | ||||
if type(obj) is cls: | ||||
return True | ||||
return False | ||||
else: | ||||
return type(obj) is check | ||||
MinRK
|
r7967 | def can(obj): | ||
"""prepare an object for pickling""" | ||||
MinRK
|
r8081 | |||
import_needed = False | ||||
MinRK
|
r7967 | for cls,canner in can_map.iteritems(): | ||
if isinstance(cls, basestring): | ||||
MinRK
|
r8081 | import_needed = True | ||
break | ||||
MinRK
|
r9138 | elif istype(obj, cls): | ||
MinRK
|
r7967 | return canner(obj) | ||
MinRK
|
r8081 | |||
if import_needed: | ||||
# perform can_map imports, then try again | ||||
# this will usually only happen once | ||||
_import_mapping(can_map, _original_can_map) | ||||
return can(obj) | ||||
MinRK
|
r7967 | return obj | ||
MinRK
|
r9000 | def can_class(obj): | ||
MinRK
|
r9002 | if isinstance(obj, class_type) and obj.__module__ == '__main__': | ||
MinRK
|
r9000 | return CannedClass(obj) | ||
else: | ||||
return obj | ||||
MinRK
|
r7967 | def can_dict(obj): | ||
"""can the *values* of a dict""" | ||||
MinRK
|
r9712 | if istype(obj, dict): | ||
MinRK
|
r3539 | newobj = {} | ||
for k, v in obj.iteritems(): | ||||
newobj[k] = can(v) | ||||
return newobj | ||||
else: | ||||
return obj | ||||
MinRK
|
r9712 | sequence_types = (list, tuple, set) | ||
MinRK
|
r7967 | def can_sequence(obj): | ||
"""can the elements of a sequence""" | ||||
MinRK
|
r9712 | if istype(obj, sequence_types): | ||
MinRK
|
r3539 | t = type(obj) | ||
return t([can(i) for i in obj]) | ||||
else: | ||||
return obj | ||||
def uncan(obj, g=None): | ||||
MinRK
|
r7967 | """invert canning""" | ||
MinRK
|
r8081 | |||
import_needed = False | ||||
MinRK
|
r7967 | for cls,uncanner in uncan_map.iteritems(): | ||
if isinstance(cls, basestring): | ||||
MinRK
|
r8081 | import_needed = True | ||
break | ||||
elif isinstance(obj, cls): | ||||
MinRK
|
r7967 | return uncanner(obj, g) | ||
MinRK
|
r8081 | |||
if import_needed: | ||||
# perform uncan_map imports, then try again | ||||
# this will usually only happen once | ||||
_import_mapping(uncan_map, _original_uncan_map) | ||||
return uncan(obj, g) | ||||
MinRK
|
r7967 | return obj | ||
def uncan_dict(obj, g=None): | ||||
MinRK
|
r9712 | if istype(obj, dict): | ||
MinRK
|
r3539 | newobj = {} | ||
for k, v in obj.iteritems(): | ||||
newobj[k] = uncan(v,g) | ||||
return newobj | ||||
else: | ||||
return obj | ||||
MinRK
|
r7967 | def uncan_sequence(obj, g=None): | ||
MinRK
|
r9712 | if istype(obj, sequence_types): | ||
MinRK
|
r3539 | t = type(obj) | ||
return t([uncan(i,g) for i in obj]) | ||||
else: | ||||
return obj | ||||
MinRK
|
r9986 | def _uncan_dependent_hook(dep, g=None): | ||
dep.check_dependency() | ||||
def can_dependent(obj): | ||||
return CannedObject(obj, keys=('f', 'df'), hook=_uncan_dependent_hook) | ||||
MinRK
|
r3539 | |||
MinRK
|
r7967 | #------------------------------------------------------------------------------- | ||
MinRK
|
r8081 | # API dictionaries | ||
MinRK
|
r7967 | #------------------------------------------------------------------------------- | ||
# These dicts can be extended for custom serialization of new objects | ||||
can_map = { | ||||
MinRK
|
r9986 | 'IPython.parallel.dependent' : can_dependent, | ||
MinRK
|
r7967 | 'numpy.ndarray' : CannedArray, | ||
FunctionType : CannedFunction, | ||||
bytes : CannedBytes, | ||||
buffer : CannedBuffer, | ||||
MinRK
|
r9002 | class_type : can_class, | ||
MinRK
|
r7967 | } | ||
uncan_map = { | ||||
CannedObject : lambda obj, g: obj.get_object(g), | ||||
} | ||||
MinRK
|
r8081 | # for use in _import_mapping: | ||
_original_can_map = can_map.copy() | ||||
_original_uncan_map = uncan_map.copy() | ||||