SND@LHC Software
Loading...
Searching...
No Matches
rootpyPickler.py
Go to the documentation of this file.
1# Copyright 2012 the rootpy developers
2# distributed under the terms of the GNU General Public License
3# Original author: Scott Snyder scott.snyder(a)cern.ch, 2004.
4
5# copied and modified by T.Ruf for the standalone use in FAIRSHIP
6"""Pickle python data into a ROOT file, preserving references to ROOT objects.
7
8This module allows pickling python objects into a ROOT file. The python
9objects may contain references to named ROOT objects. If one has set up a
10structure of python objects to hold ROOT histograms, this provides a convenient
11way of saving and restoring your histograms. The pickled python data are
12stored in an additional string object in the ROOT file; any ROOT objects are
13stored as usual. (Thus, ROOT files written by the pickler can be read just
14like any other ROOT file if you don't care about the python data.)
15
16Here's an example of writing a pickle::
17
18from rootpyPickler import Pickler
19pkl=Pickler(file)
20pkl.dump(ShipGeo,'ShipGeo')
21
22from rootpyPickler import Unpickler
23upkl = Unpickler(f)
24ShipGeo = upkl.load('ShipGeo')
25
26The following additional notes apply:
27
28* Pickling may not always work correctly for the case of python objects
29 deriving from ROOT objects. It will probably also not work for the case of
30 ROOT objects which do not derive from TObject.
31
32* When the pickled data are being read, if a class doesn't exist,
33 a dummy class with no methods will be used instead. This is different
34 from the standard pickle behavior (where it would be an error), but it
35 simplifies usage in the common case where the class is being used to hold
36 histograms, and its methods are entirely concerned with filling the
37 histograms.
38
39* When restoring a reference to a ROOT object, the default behavior
40 is to not read the ROOT object itself, but instead to create a proxy. The
41 ROOT object will then be read the first time the proxy is accessed. This can
42 help significantly with time and memory usage if you're only accessing a
43 small fraction of the ROOT objects, but it does mean that you need to keep
44 the ROOT file open. Pass use_proxy=0 to disable this behavior.
45
46"""
47from __future__ import absolute_import
48from __future__ import print_function
49
50from past.builtins import basestring
51import sys
52if sys.version_info[0] < 3:
53 from cStringIO import StringIO
54else:
55 from io import StringIO
56
57# need subclassing ability in 2.x
58import pickle
59
60import ROOT
61
62string_types = basestring,
63if sys.version_info[0] < 3:
64 integer_types = (int, long)
65else:
66 integer_types = (int,)
67
68__all__ = [
69 'dump',
70 'load',
71 'compat_hooks',
72]
73
74
75_compat_hooks = None
76xdict = {}
77xserial = 0
78
79"""
80Argh! We can't store NULs in TObjStrings.
81But pickle protocols > 0 are binary protocols, and will get corrupted
82if we truncate at a NUL.
83So, when we save the pickle data, make the mappings:
84
85 0x00 -> 0xff 0x01
86 0xff -> 0xff 0xfe
87
88"""
89
90
91def _protect(s):
92 return s.replace(b'\377', b'\377\376').replace(b'\000', b'\377\001')
93
94
95def _restore(s):
96 return s.replace(b'\377\001', b'\000').replace(b'\377\376', b'\377')
97
98
100 def __init__(self):
101 return self.reopen()
102
103 def write(self, s):
104 return self.__s.write(_protect(s).decode('utf-8'))
105
106 def read(self, i):
107 return self.__s.read(i).encode('utf-8')
108
109 def readline(self):
110 return self.__s.readline().encode('utf-8')
111
112 def getvalue(self):
113 return self.__s.getvalue()
114
115 def setvalue(self, s):
116 self.__s = StringIO(_restore(s.encode('utf-8')).decode('utf-8'))
117 return
118
119 def reopen(self):
120 self.__s = StringIO()
121 return
122
123
125 def __init__(self, f, pid):
126 self.__f = f
127 self.__pid = pid
128 self.__o = None
129
130 def __getattr__(self, a):
131 if self.__o is None:
132 self.__o = self.__f.Get(self.__pid)
133 if self.__o.__class__.__module__ != 'ROOT':
134 self.__o.__class__.__module__ = 'ROOT'
135 return getattr(self.__o, a)
136
137 def __obj(self):
138 if self.__o is None:
139 self.__o = self.__f.Get(self.__pid)
140 if self.__o.__class__.__module__ != 'ROOT':
141 self.__o.__class__.__module__ = 'ROOT'
142 return self.__o
143
144
145class Pickler(pickle.Pickler):
146 def __init__(self, file, proto=0):
147 """Create a root pickler.
148 `file` should be a ROOT TFile. `proto` is the python pickle protocol
149 version to use. The python part will be pickled to a ROOT
150 TObjString called _pickle; it will contain references to the
151 ROOT objects.
152 """
153 self.__file = file
154 self.__keys = file.GetListOfKeys()
156 self.__pmap = {}
157 if sys.version_info[0] < 3:
158 # 2.X old-style classobj
159 pickle.Pickler.__init__(self, self.__io, proto)
160 else:
161 super(Pickler, self).__init__(self.__io, proto)
162
163 def dump(self, obj, key=None):
164 """Write a pickled representation of obj to the open TFile."""
165 if key is None:
166 key = '_pickle'
167 if 1>0:
168 self.__file.cd()
169 if sys.version_info[0] < 3:
170 pickle.Pickler.dump(self, obj)
171 else:
172 super(Pickler, self).dump(obj)
173 s = ROOT.TObjString(self.__io.getvalue())
174 self.__io.reopen()
175 s.Write(key)
176 self.__file.GetFile().Flush()
177 self.__pmap.clear()
178
179 def clear_memo(self):
180 """Clears the pickler's internal memo."""
181 self.__pickle.memo.clear()
182
183 def persistent_id(self, obj):
184 if hasattr(obj, '_ROOT_Proxy__obj'):
185 obj = obj._ROOT_Proxy__obj()
186 if isinstance(obj, ROOT.TObject):
187 """
188 Write the object, and return the resulting NAME;CYCLE.
189 We used to do this::
190
191 o.Write()
192 k = self.__file.GetKey(o.GetName())
193 pid = "{0};{1:d}".format(k.GetName(), k.GetCycle())
194
195 It turns out, though, that destroying the python objects
196 referencing the TKeys is quite expensive (O(logN) where N is the
197 total number of pyroot objects?). Although we want to allow for
198 the case of saving multiple objects with the same name, the most
199 common case is that the name has not already been written to the
200 file. So we optimize for that case, doing the key lookup before we
201 write the object, not after. (Note further: GetKey() is very slow
202 if the key does not actually exist, as it does a linear search of
203 the key list. We use FindObject instead for the initial
204 lookup, which is a hashed lookup, but it is not guaranteed to
205 find the highest cycle. So if we do find an existing key, we
206 need to look up again using GetKey.
207 """
208 nm = obj.GetName()
209 key = self.__keys.FindObject(nm)
210 obj.Write()
211 if key:
212 key = self.__file.GetKey(nm)
213 pid = '{0};{1:d}'.format(nm, key.GetCycle())
214 else:
215 pid = nm + ';1'
216 return pid
217
218
219class Unpickler(pickle.Unpickler):
220 def __init__(self, root_file, use_proxy=True, use_hash=False):
221 """Create a ROOT unpickler.
222 `file` should be a ROOT TFile.
223 """
224 global xserial
225 xserial += 1
226 self.__use_proxy = use_proxy
227 self.__file = root_file
229 self.__n = 0
230 self.__serial = '{0:d}-'.format(xserial).encode('utf-8')
231 xdict[self.__serial] = root_file
232 if sys.version_info[0] < 3:
233 pickle.Unpickler.__init__(self, self.__io)
234 else:
235 super(Unpickler, self).__init__(self.__io)
236
237 if use_hash:
238 htab = {}
239 ctab = {}
240 for k in root_file.GetListOfKeys():
241 nm = k.GetName()
242 cy = k.GetCycle()
243 htab[(nm, cy)] = k
244 if cy > ctab.get(nm, 0):
245 ctab[nm] = cy
246 htab[(nm, 9999)] = k
247 root_file._htab = htab
248 oget = root_file.Get
249
250 def xget(nm0):
251 nm = nm0
252 ipos = nm.find(';')
253 if ipos >= 0:
254 cy = nm[ipos+1]
255 if cy == '*':
256 cy = 10000
257 else:
258 cy = int(cy)
259 nm = nm[:ipos - 1]
260 else:
261 cy = 9999
262 ret = htab.get((nm, cy), None)
263 if not ret:
264 print(("warning didn't find {0} {1} {2}",nm, cy, len(htab) ))
265 return oget(nm0)
266 #ctx = ROOT.TDirectory.TContext(file)
267 ret = ret.ReadObj()
268 #del ctx
269 return ret
270 root_file.Get = xget
271
272 def load(self, skey=None):
273 """Read a pickled object representation from the open file."""
274 key = skey
275 if skey is None:
276 key = '_pickle'
277 elif skey.find(';')<0: key = skey+';'
278 obj = None
279 if _compat_hooks:
280 save = _compat_hooks[0]()
281 try:
282 self.__n += 1
283 s = self.__file.Get(key + ';{0:d}'.format(self.__n))
284 self.__io.setvalue(s.GetName())
285 if sys.version_info[0] < 3:
286 obj = pickle.Unpickler.load(self)
287 else:
288 obj = super(Unpickler, self).load()
289 self.__io.reopen()
290 finally:
291 if _compat_hooks:
292 save = _compat_hooks[1](save)
293 return obj
294
295 def persistent_load(self, pid):
296 if self.__use_proxy:
297 obj = ROOT_Proxy(self.__file, pid.decode('utf-8'))
298 else:
299 obj = self.__file.Get(pid)
300 #log.debug("load {0} {1}".format(pid, obj))
301 xdict[self.__serial + pid] = obj
302 return obj
303
304 def find_class(self, module, name):
305 try:
306 try:
307
312 if sys.version_info[0] >2:
313 if module == 'copy_reg': module = 'copyreg'
314 if module == '__builtin__': module = 'builtins'
315 __import__(module)
316 mod = sys.modules[module]
317 except ImportError:
318 #log.info("Making dummy module {0}".format(module))
319
320 class DummyModule:
321 pass
322
323 mod = DummyModule()
324 sys.modules[module] = mod
325 klass = getattr(mod, name)
326 return klass
327 except AttributeError:
328 #log.info("Making dummy class {0}.{1}".format(module, name))
329 mod = sys.modules[module]
330
331 class Dummy(object):
332 pass
333
334 setattr(mod, name, Dummy)
335 return Dummy
336
337 # Python 2.x
338 find_global = find_class
339
340
341def compat_hooks(hooks):
342 """Set compatibility hooks.
343 If this is set, then hooks[0] is called before loading, and hooks[1] is
344 called after loading. hooks[1] is called with the return value of hooks[0]
345 as an argument. This is useful for backwards compatibility in some
346 situations.
347 """
348 global _compat_hooks
349 _compat_hooks = hooks
350
351
352def dump(obj, root_file, proto=0, key=None):
353 """Dump an object into a ROOT TFile.
354
355 `root_file` may be an open ROOT file or directory, or a string path to an
356 existing ROOT file.
357 """
358 if isinstance(root_file, string_types):
359 root_file = ROOT.TFile.Open(root_file, 'recreate')
360 own_file = True
361 else:
362 own_file = False
363 ret = Pickler(root_file, proto).dump(obj, key)
364 if own_file:
365 root_file.Close()
366 return ret
367
368
369def load(root_file, use_proxy=1, key=None):
370 """Load an object from a ROOT TFile.
371
372 `root_file` may be an open ROOT file or directory, or a string path to an
373 existing ROOT file.
374 """
375 if isinstance(root_file, string_types):
376 root_file = ROOT.TFile.Open(root_file)
377 own_file = True
378 else:
379 own_file = False
380 obj = Unpickler(root_file, use_proxy).load(key)
381 if own_file:
382 root_file.Close()
383 return obj
dump(self, obj, key=None)
__init__(self, file, proto=0)
__init__(self, root_file, use_proxy=True, use_hash=False)
find_class(self, module, name)
load(self, skey=None)
load(root_file, use_proxy=1, key=None)
dump(obj, root_file, proto=0, key=None)