First of all, I am aware of this question.
I have the same problem. I am modifying an object "Cell" one at a time from a list of said cells. But the problem is modifying one maps all the modifications to every other element of the list too. Deepcopy is not a viable solution for me, as it increases the computation time by way too much.
The problem is simple, and I understand it, all the cells in the list point to same object. But I really don't understand how. For example list is a object in Python, but when i modify a list, not all lists are modified right? Sorry if I sound dumb, but I think I don't really understand how objects in Python are dealt with. Please provide any insight that might be helpful. I will post my code below. You can find a TL;DR after all the code snippets.
First of all the classes that are used:
@dataclass(slots=True)
class Leaves:
nlen: int = 0
leaf_cells: List['Cell'] = None # containing cell objects
@dataclass(slots=True)
class Neighbor:
n: int = 0
idx: np.ndarray = None
@dataclass(slots=True)
class Cell:
xmin: float = 0.0
xmax: float = 0.0
ymin: float = 0.0
ymax: float = 0.0
val: np.ndarray = np.zeros(NumOfBasicParam)
using: bool = False
converged: bool = False
id: int = -1
order: int = 0
nChildren: int = 0
nOffspring: int = 0
nleaves: int = 0
parent: Optional["Cell"] = None
children: List["Cell"] = field(default_factory=list)
inner: Neighbor = field(default_factory=Neighbor)
outer: Neighbor = field(default_factory=Neighbor)
below: Neighbor = field(default_factory=Neighbor)
above: Neighbor = field(default_factory=Neighbor)
around: Neighbor = field(default_factory=Neighbor)
Now the functions to make leaves
def grid_make_leaves(croot: Cell
) -> Tuple[Cell, Leaves, int]:
leaves = Leaves(croot.nleaves)
# for i in range(leaves.nlen):
# leaves.list[i].id = - 1
# leaves.nlen = croot.nleaves
leaves.list = [Cell() for i in range(leaves.nlen)]
idx = -1
croot, leaves, idx = grid_add_leaves(croot, leaves, idx)
return croot, leaves, idx
def grid_add_leaves(c: Cell,
leaves: Leaves,
idx: int,
) -> Tuple[Cell, Leaves, int]:
if c.using:
idx = idx + 1
leaves.list[idx] = c
leaves.list[idx].id = idx
return c, leaves, idx
else:
for i in range(c.nChildren):
c.children[i], leaves, idx = grid_add_leaves(c.children[i], leaves, idx)
return c, leaves, idx
Finally function which makes neighbors.
def grid_make_neighbors(grid_config: GridConfig,
leaves: Leaves
) -> Leaves:
max_neigh = 0
idx = 0
leaves_dict = asdict(leaves)
# print(leaves_dict.keys())
# instead of using leaves objects all the time
# use something like for leaf_cell in leaves.list:
# for i in tqdm(range(leaves.nlen)):
for i, c in tqdm(enumerate(leaves.list)):
nnei_max = 512
idx_inner = np.zeros(nnei_max)
idx_outer = np.zeros(nnei_max)
idx_below = np.zeros(nnei_max)
idx_above = np.zeros(nnei_max)
c.inner.n = 0
c.outer.n = 0
c.above.n = 0
c.below.n = 0
c.around.n = 0
for j, c2 in enumerate(leaves.list):
if i == j:
continue
tol = min(1e-3 * min(c.xmax - c.xmin, c.ymax - c.ymin,
c2.xmax - c2.xmin, c2.ymax - c2.ymin),
grid_config.very_small_len)
neighbor_cell, pos, frac = is_neighbor(c, c2, tol)
if neighbor_cell:
match pos:
case 1:
c.inner.n = c.inner.n + 1
if c.inner.n > nnei_max:
print("In make_neighbors: ")
print("c.inner.n too large", c.inner.n, nnei_max)
exit()
idx_inner[c.inner.n-1] = j + 1
case 2:
c.outer.n = c.outer.n + 1
if c.outer.n > nnei_max:
print("In make_neighbors: ")
print("c.outer.n too large", c.inner.n, nnei_max)
exit()
idx_outer[c.outer.n-1] = j + 1
case 3:
c.below.n = c.below.n + 1
if c.below.n > nnei_max:
print("In make_neighbors: ")
print("c.below.n too large", c.inner.n, nnei_max)
exit()
idx_below[c.below.n-1] = j + 1
case 4:
c.above.n = c.above.n + 1
if c.above.n > nnei_max:
print("In make_neighbors: ")
print("c.below.n too large", c.inner.n, nnei_max)
exit()
idx_above[c.above.n-1] = j + 1
c.around.n = c.inner.n + c.outer.n + c.above.n + c.below.n
if c.around.n > 0:
c.around.idx = np.zeros(c.around.n)
k = 0
if c.above.n > 0:
c.above.idx = idx_above[0:c.above.n]
c.around.idx[k:k+c.above.n] = c.above.idx
k = k + c.above.n
if c.inner.n > 0:
c.inner.idx = idx_inner[0:c.inner.n]
c.around.idx[k:k+c.inner.n] = c.inner.idx
k = k + c.inner.n
if c.outer.n > 0:
c.outer.idx = idx_outer[0:c.outer.n]
c.around.idx[k:k+c.outer.n] = c.outer.idx
k = k + c.outer.n
if c.below.n > 0:
c.below.idx = idx_below[0:c.below.n]
c.around.idx[k:k+c.below.n] = c.below.idx
k = k + c.below.n
if max_neigh < c.around.n:
max_neigh = c.around.n
idx = i
print(idx, c.above.n, c.below.n, c.inner.n, c.outer.n, c.around.n)
# leaves.list[i] = copy.deepcopy(c)
# if i == 30:
# break
# print(leaves.list[8559].above.n, leaves.list[8559].below.n, leaves.list[8559].inner.n, leaves.list[8559].outer.n, leaves.list[8559].around.n)
print("Max number of neighbors:", max_neigh)
print("owned by this one: xmin,xmax,ymin,ymax", c.xmin, c.xmax, c.ymin, c.ymax)
# print(idx, leaves_new.list[idx].above.n, leaves_new.list[idx].below.n, leaves_new.list[idx].inner.n, leaves_new.list[idx].outer.n, leaves_new.list[idx].around.n) # Prints the correct number of neighbors
return leaves
It is fine if you did not go through the code: leaves
is an instance of Leaves
object with an attribute list
. leaves.list
is a list of Cell
type objects. When I modify each Cell
object of leaves.list
in a loop, all members of leaves.list
are modified. I would like only the member which I'm using at any given moment is modified. Deepcopy is too slow, so not a viable solution. In the process, I would really like if someone can properly explain why and how Python objects work like this.
Thank you.
Instead of using default_factory
or initialising attributes as objects when defining the dataclass was causing the issue. On the advice of Tim, I changed everything to None by default, and initialised them as Neighbor
objects only when I needed them.
So only following changes were made:
@dataclass(slots=True)
class Cell:
xmin: float = 0.0
xmax: float = 0.0
ymin: float = 0.0
ymax: float = 0.0
val: np.ndarray = np.zeros(NumOfBasicParam)
using: bool = False
converged: bool = False
id: int = -1
order: int = 0
nChildren: int = 0
nOffspring: int = 0
nleaves: int = 0
parent: Optional["Cell"] = None
children: List["Cell"] = None
inner: Neighbor = None
outer: Neighbor = None
below: Neighbor = None
above: Neighbor = None
around: Neighbor = None