Card 06/ 08
Shallow Copies That Share a List Between Clones
Add a weapon to one goblin's inventory, and every goblin copied from it carries it too.
A shallow copy creates a new object but copies only references to what the original contains, so a list or dictionary inside it is shared by the original and every copy. Python's copy.copy, Java's Object.clone() and JavaScript's spread operator all copy this way, and a prototype that uses one gives every clone the same list. This card asks you to predict the result first.
A goblin prototype carries ["club"]. Two goblins are copied from it with copy.copy, and the first picks up a bow.
goblin = Enemy("goblin", ["club"])
first = copy.copy(goblin)
second = copy.copy(goblin)
first.gear.append("bow")What does the second goblin carry? Decide, then look.
import copy
class Enemy:
def __init__(self, kind: str, gear: list[str]) -> None:
self.kind = kind
self.gear = gear
goblin = Enemy("goblin", ["club"])
first = copy.copy(goblin)
second = copy.copy(goblin)
first.gear.append("bow")
print("goblin:", goblin.gear)
print("first: ", first.gear)
print("second:", second.gear)
print("one list:", first.gear is second.gear is goblin.gear)
fixed = copy.deepcopy(goblin)
fixed.gear.append("torch")
print("after deepcopy, goblin:", goblin.gear, "fixed:", fixed.gear)goblin: ['club', 'bow']
first: ['club', 'bow']
second: ['club', 'bow']
one list: True
after deepcopy, goblin: ['club', 'bow'] fixed: ['club', 'bow', 'torch']All three carry the bow, the prototype included, because copy.copy created two new Enemy objects whose gear attribute refers to the prototype's one list. Appending through any of the three changes the list all three see. The last line shows the fix: copy.deepcopy copies the list as well, so the torch goes into the copy's list alone.
Deep copying is not free. It copies everything reachable, which can be large, and it copies things that should be shared, such as a reference to the game's map. So a careful clone() decides field by field: values that cannot change, such as strings and numbers, can be shared; lists and dictionaries the clone will change need their own copy; and references to shared things stay shared. Python's __deepcopy__ and a hand-written clone() are where that decision is written down.
← → move through sections and cards