Clarifying the behavior of copy.copy() and copy.deepcopy() for MultiIndexSet
Currently, the MultiIndexSet
class has implementations of two dunder methods __copy__()
and __deepcopy__()
that define the custom behavior of the call to copy.copy()
and copy.deepcopy()
on the instances, respectively.
Expected behavior of shallow and deep copies
Let's confirm what do we expect when we create a shallow and a deep copy of a MultiIndexSet
instances:
- shallow copy means the underlying exponents are not copied. An instance being copied shares the same underlying exponents with the ones in the copy. This is fine for most use cases because we don't want any direct changes to the underlying exponents object. When a modification does happen via one of the methods (e.g.,
add_exponents
), a new instance will be created first. We strongly discourage any direct modifications to the underlying exponents (becausemi.exponents[idx]
are technically still accessible). - deep copy means the underlying exponents are copied. An instance being copied does not share the same exponents with the ones in the copy. This method is implemented for cases that require a truly independent instance.
The current copy.copy()
works as expected:
>>> import minterpy as mp
>>> import numpy as np
>>> import copy
>>> mi = mp.MultiIndexSet.from_degree(2, 2, 1)
>>> mi_copy = copy.copy(mi)
>>> mi is mi_copy
False
>>> mi.exponents is mi_copy.exponents
True
>>> np.shares_memory(mi.exponents, mi_copy.exponents)
True
as well as the copy.deepcopy()
:
>>> mi_copy = copy.copy(mi)
>>> mi is mi_copy
False
>>> mi.exponents is mi_copy.exponents
False
>>> np.shares_memory(mi.exponents, mi_copy.exponents)
False
The rest of the main properties, i.e.:
-
lp_degree
(_lp_degree
) is a Python built-infloat
-
poly_degree
(_poly_degree
) is a Python built-inint
-
is_complete
(_is_complete
) is a Python built-inbool
are immutable primitives so I guess it doesn't really matter whether they are shallow copies or deep copies; the values will be assigned as attributes of the copy as is.
Refactoring ideas
The thing is, to achieve the above expected behaviors, I don't think we need to define the custom dunder methods at all. If we rely on the default behaviors of copy.copy()
and copy.deepcopy()
we get exactly the same behaviors (tested with Python v3.8).
Are there any specific reasons of defining the dunder methods? If not, perhaps we can just remove them.