Skip to content

Commit 09df730

Browse files
authored
Merge pull request biolab#6849 from markotoplak/deepcopy-outermost
[FIX] Only deepcopy the .attributes for the outermost Table transformation
2 parents 4b68a84 + 4460fbb commit 09df730

File tree

2 files changed

+35
-11
lines changed

2 files changed

+35
-11
lines changed

Orange/data/table.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -792,6 +792,16 @@ def from_table(cls, domain, source, row_indices=...):
792792
:return: a new table
793793
:rtype: Orange.data.Table
794794
"""
795+
if domain is source.domain:
796+
table = cls.from_table_rows(source, row_indices)
797+
# assure resulting domain is the instance passed on input
798+
table.domain = domain
799+
# since sparse flags are not considered when checking for
800+
# domain equality, fix manually.
801+
with table.unlocked_reference():
802+
table = assure_domain_conversion_sparsity(table, source)
803+
return table
804+
795805
new_cache = _thread_local.conversion_cache is None
796806
try:
797807
if new_cache:
@@ -801,15 +811,6 @@ def from_table(cls, domain, source, row_indices=...):
801811
cached = _idcache_restore(_thread_local.conversion_cache, (domain, source))
802812
if cached is not None:
803813
return cached
804-
if domain is source.domain:
805-
table = cls.from_table_rows(source, row_indices)
806-
# assure resulting domain is the instance passed on input
807-
table.domain = domain
808-
# since sparse flags are not considered when checking for
809-
# domain equality, fix manually.
810-
with table.unlocked_reference():
811-
table = assure_domain_conversion_sparsity(table, source)
812-
return table
813814

814815
# avoid boolean indices; also convert to slices if possible
815816
row_indices = _optimize_indices(row_indices, len(source))
@@ -834,7 +835,9 @@ def from_table(cls, domain, source, row_indices=...):
834835
self.W = source.W[row_indices]
835836
self.name = getattr(source, 'name', '')
836837
self.ids = source.ids[row_indices]
837-
self.attributes = deepcopy(getattr(source, 'attributes', {}))
838+
self.attributes = getattr(source, 'attributes', {})
839+
if new_cache: # only deepcopy attributes for the outermost transformation
840+
self.attributes = deepcopy(self.attributes)
838841
_idcache_save(_thread_local.conversion_cache, (domain, source), self)
839842
return self
840843
finally:
@@ -879,6 +882,7 @@ def from_table_rows(cls, source, row_indices):
879882
:return: a new table
880883
:rtype: Orange.data.Table
881884
"""
885+
is_outermost_transformation = _thread_local.conversion_cache is None
882886
self = cls()
883887
self.domain = source.domain
884888
with self.unlocked_reference():
@@ -892,7 +896,9 @@ def from_table_rows(cls, source, row_indices):
892896
self.W = source.W[row_indices]
893897
self.name = getattr(source, 'name', '')
894898
self.ids = source.ids[row_indices]
895-
self.attributes = deepcopy(getattr(source, 'attributes', {}))
899+
self.attributes = getattr(source, 'attributes', {})
900+
if is_outermost_transformation:
901+
self.attributes = deepcopy(self.attributes)
896902
return self
897903

898904
@classmethod

Orange/tests/test_table.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2068,6 +2068,24 @@ def test_attributes_copied(self):
20682068
# attributes dict of old table not be changed since new dist is a copy
20692069
self.assertDictEqual(self.table.attributes, {"A": "Test", "B": []})
20702070

2071+
def test_attributes_copied_once(self):
2072+
A = Mock()
2073+
A.__deepcopy__ = Mock()
2074+
self.table.attributes = {"A": A}
2075+
2076+
# a single direct transformation
2077+
self.table.from_table(self.table.domain, self.table)
2078+
self.assertEqual(1, A.__deepcopy__.call_count)
2079+
A.__deepcopy__.reset_mock()
2080+
2081+
# hierarchy of transformations
2082+
ndom = Domain([a.copy(compute_value=lambda x: x.transform(Domain([a])))
2083+
for a in self.table.domain.attributes])
2084+
self.table.from_table(ndom, self.table)
2085+
self.assertEqual(1, A.__deepcopy__.call_count)
2086+
# HISTORIC: before only the outermost transformation deepcopied the
2087+
# attributes, here were 23 calls to __deepcopy__ instead of 1
2088+
20712089

20722090
def isspecial(s):
20732091
return isinstance(s, slice) or s is Ellipsis

0 commit comments

Comments
 (0)