Skip to content

Commit 53e1948

Browse files
committed
domain: fix unpickling with circles
Unpickling dictionaries that include objects that redefine __hash__ as keys is sometimes problematic (when said objects do not have __dict__ filled-in yet in but are used as keys in a restored dictionary).
1 parent d67cc59 commit 53e1948

File tree

1 file changed

+30
-8
lines changed

1 file changed

+30
-8
lines changed

Orange/data/domain.py

+30-8
Original file line numberDiff line numberDiff line change
@@ -164,17 +164,38 @@ def __init__(self, attributes, class_vars=None, metas=None, source=None):
164164
if not all(var.is_primitive() for var in self._variables):
165165
raise TypeError("variables must be primitive")
166166

167-
self._indices = dict(chain.from_iterable(
168-
((var, idx), (var.name, idx), (idx, idx))
169-
for idx, var in enumerate(self._variables)))
170-
self._indices.update(chain.from_iterable(
171-
((var, -1-idx), (var.name, -1-idx), (-1-idx, -1-idx))
172-
for idx, var in enumerate(self.metas)))
167+
self._indices = None
173168

174169
self.anonymous = False
175170

176171
self._hash = None # cache for __hash__()
177172

173+
def _ensure_indices(self):
174+
if self._indices is None:
175+
self._indices = dict(chain.from_iterable(
176+
((var, idx), (var.name, idx), (idx, idx))
177+
for idx, var in enumerate(self._variables)))
178+
self._indices.update(chain.from_iterable(
179+
((var, -1-idx), (var.name, -1-idx), (-1-idx, -1-idx))
180+
for idx, var in enumerate(self.metas)))
181+
182+
def __setstate__(self, state):
183+
self.__dict__.update(state)
184+
self._variables = self.attributes + self.class_vars
185+
self._indices = None
186+
self._hash = None
187+
188+
def __getstate__(self):
189+
# Do not pickle dictionaries because unpickling dictionaries that
190+
# include objects that redefine __hash__ as keys is sometimes problematic
191+
# (when said objects do not have __dict__ filled yet in but are used as
192+
# keys in a restored dictionary).
193+
state = self.__dict__.copy()
194+
del state["_variables"]
195+
del state["_indices"]
196+
del state["_hash"]
197+
return state
198+
178199
# noinspection PyPep8Naming
179200
@classmethod
180201
def from_numpy(cls, X, Y=None, metas=None):
@@ -289,7 +310,7 @@ def __getitem__(self, idx):
289310
"""
290311
if isinstance(idx, slice):
291312
return self._variables[idx]
292-
313+
self._ensure_indices()
293314
index = self._indices.get(idx)
294315
if index is None:
295316
var = self._get_equivalent(idx)
@@ -306,6 +327,7 @@ def __contains__(self, item):
306327
Return `True` if the item (`str`, `int`, :class:`Variable`) is
307328
in the domain.
308329
"""
330+
self._ensure_indices()
309331
return item in self._indices or self._get_equivalent(item) is not None
310332

311333
def __iter__(self):
@@ -334,7 +356,7 @@ def index(self, var):
334356
Return the index of the given variable or meta attribute, represented
335357
with an instance of :class:`Variable`, `int` or `str`.
336358
"""
337-
359+
self._ensure_indices()
338360
idx = self._indices.get(var)
339361
if idx is not None:
340362
return idx

0 commit comments

Comments
 (0)