Prototype pattern enables us to create an object from an existing object(by copying or cloning it), rather than creating an object from scratch. This process saves the overhead of the initialization process required for creating a new object from scratch.
In this pattern we copy/clone an existing object, to create a new object, and then change the attributes as per our new object requirement.
NOTES
In this article, we discuss the implementation of the Prototype Pattern in Python.
See the Prototype in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Prototype Pattern-
Implementation
The implementation of the prototype pattern fully depends on the copy/clone process of the programming language. In Python, we have the package “copy” for performing a copy/clone of an object.
NOTES
We have discussed multiple ways of implementing object cloning. Deep copying is the most recommended method for these.
But you should read and know about all the variations of the implementation.
Also, you can clone an object by simply using copy.copy(existing_object), or copy.deepcopy(existing_object).
Method #1: Copy to Clone
import copy
class Item:
def __init__(self, name, data):
self.name = name
self.data = data
def clone(self):
return copy.copy(self)
def __str__(self):
return f"Item: (name={self.name}, data={self.data})"
# Demo usage
# Original object
original_obj = Item("Original", 100)
print("Original:", original_obj)
# Clone/copy
clone_obj = original_obj.clone()
print("Clone (Just after cloning):", clone_obj)
# Change attributes for this new object
clone_obj.name = "Clone Object"
clone_obj.data = 999
print("Clone Object(After data change): ", clone_obj)
# Check original
print("Original Object(Checking after cloning): ", original_obj)
PythonOutput:
Original: Item: (name=Original, data=100)
Clone (Just after cloning): Item: (name=Original, data=100)
Clone Object(After data change): Item: (name=Clone Object, data=999)
Original Object(Checking after cloning): Item: (name=Original, data=100)
PlaintextMethod #2: Abstract Prototype
This is an improvement over the last discussed method-
This way we can use the same prototype implementation for multiple classes.
import copy
class Prototype:
def clone(self):
return copy.copy(self)
class Item(Prototype):
def __init__(self, name, data):
self.name = name
self.data = data
def __str__(self):
return f"Item: (name={self.name}, data={self.data})"
# Demo usage
# Original object
original_obj = Item("Original", 100)
print("Original:", original_obj)
# Clone/copy
clone_obj = original_obj.clone()
print("Clone (Just after cloning):", clone_obj)
# Change attributes for this new object
clone_obj.name = "Clone Object"
clone_obj.data = 999
print("Clone Object(After data change): ", clone_obj)
# Check original
print("Original Object(Checking after cloning): ", original_obj)
PythonOutput:
Original: Item: (name=Original, data=100)
Clone (Just after cloning): Item: (name=Original, data=100)
Clone Object(After data change): Item: (name=Clone Object, data=999)
Original Object(Checking after cloning): Item: (name=Original, data=100)
PlaintextMethod #3: Deep Copy
When we use “copy.copy()” it performs a shallow copy. Let’s see what problems we face with shallow copy-
Problem with Shallow Copy
We see issues with “copy.copy()” when an object has another object (nested) inside it.
NOTES
Check the code below- we have an object of the class “Demo” inside the class “Item” saved in the attribute “demo_obj“.
import copy
class Prototype:
def clone(self) -> "Prototype":
return copy.copy(self)
class Demo:
def __init__(self, prop1: str, prop2: int) -> None:
self.prop1 = prop1
self.prop2 = prop2
def __str__(self):
return f"Demo: (prop1={self.prop1}, prop2={self.prop2})"
class Item(Prototype):
def __init__(self, name, data):
self.name = name
self.data = data
self.demo_obj = Demo("My Demo", 123)
def __str__(self):
return f"Item: (name={self.name}, data={self.data}, demo_obj: {self.demo_obj})"
# Demo usage
# Original object
original_obj = Item("Original", 100)
print("Original:", original_obj)
# Clone/copy
clone_obj = original_obj.clone()
print("Clone (Just after cloning):", clone_obj)
# Change attributes for this new object
clone_obj.name = "Clone Object"
clone_obj.data = 999
clone_obj.demo_obj.prop1 = "Changed in clone"
print("Clone Object(After data change): ", clone_obj)
# Check original
print("Original Object(Checking after cloning): ", original_obj)
PythonOutput:
NOTES
Notice that – the value of “prop1” of “Demo” object inside original object is also change when we change the cloned object.
Because both the “Item” objects are referring to the same “Demo” object.
Original: Item: (name=Original, data=100, demo_obj: Demo: (prop1=My Demo, prop2=123))
Clone (Just after cloning): Item: (name=Original, data=100, demo_obj: Demo: (prop1=My Demo, prop2=123))
Clone Object(After data change): Item: (name=Clone Object, data=999, demo_obj: Demo: (prop1=Changed in clone, prop2=123))
Original Object(Checking after cloning): Item: (name=Original, data=100, demo_obj: Demo: (prop1=Changed in clone, prop2=123))
PlaintextSolve Deep Object Cloning [Recommended]
To overcome the problems with shallow copy, we have to use “copy.deepcopy()“. Just use “copy.deepcopy()” in place of “copy.copy()“.
This will perform a deep copy including the nested objects.
import copy
class Prototype:
def clone(self) -> "Prototype":
return copy.deepcopy(self)
class Demo:
def __init__(self, prop1: str, prop2: int) -> None:
self.prop1 = prop1
self.prop2 = prop2
def __str__(self):
return f"Demo: (prop1={self.prop1}, prop2={self.prop2})"
class Item(Prototype):
def __init__(self, name, data):
self.name = name
self.data = data
self.demo_obj = Demo("My Demo", 123)
def __str__(self):
return f"Item: (name={self.name}, data={self.data}, demo_obj: {self.demo_obj})"
# Demo usage
# Original object
original_obj = Item("Original", 100)
print("Original:", original_obj)
# Clone/copy
clone_obj = original_obj.clone()
print("Clone (Just after cloning):", clone_obj)
# Change attributes for this new object
clone_obj.name = "Clone Object"
clone_obj.data = 999
clone_obj.demo_obj.prop1 = "Changed in clone"
print("Clone Object(After data change): ", clone_obj)
# Check original
print("Original Object(Checking after cloning): ", original_obj)
PythonOutput:
Original: Item: (name=Original, data=100, demo_obj: Demo: (prop1=My Demo, prop2=123))
Clone (Just after cloning): Item: (name=Original, data=100, demo_obj: Demo: (prop1=My Demo, prop2=123))
Clone Object(After data change): Item: (name=Clone Object, data=999, demo_obj: Demo: (prop1=Changed in clone, prop2=123))
Original Object(Checking after cloning): Item: (name=Original, data=100, demo_obj: Demo: (prop1=My Demo, prop2=123))
PlaintextExamples
We have discussed 2 examples of prototyping, one with table cells and another with vehicle prototyping.
Example #1: Table Cell
When we print a table, we represent each cell with an object. If we create thousands of cell objects from scratch, the overhead of initialization becomes heavy.
We can use the prototype pattern here, to clone existing cells and then change data in the cloned object.
Cell Class
import copy
class CellPrototype:
def clone(self, row: int = None, column: int = None) -> "Prototype":
clone_obj = copy.deepcopy(self)
if row is not None:
clone_obj.row = row
if column is not None:
clone_obj.column = column
return clone_obj
class Cell(CellPrototype):
def __init__(self, row: int, column: int) -> None:
self.row = row
self.column = column
# Default values
self.height: int = 10
self.width: int = 100
self.content: str | None = None
self.background = "FFFFFF"
self.text_color = "000000"
def __str__(self):
return f"""Cell: (
row={self.row},
column={self.column},
height: {self.height},
width: {self.width},
content: {self.content},
background: {self.background},
text_color: {self.text_color})"""
PythonDemo
Create on “Cell” object, and then we can use the same object to create many more objects by cloning it.
# Demo usage
def main():
# Create Cell object
cell = Cell(1, 1)
cell.setContent = "Original cell content"
cell.setBackground = "808080"
print("Cell object: ", cell)
# Clone Cell object
cell_clone = cell.clone()
print("Cell clone(without any change): ", cell_clone)
# Change values in clone
cell_clone.row = 1
cell_clone.column = 2
cell_clone.content = "Clone cell"
cell_clone.background = "008000"
cell_clone.text_color = "FFFFFF"
print("Clone object after changing:", cell_clone)
# Check the original cell object
print("Original cell object: ", cell)
# Second clone with custom row and column
second_clone = cell.clone(row=2, column=2)
second_clone.content = "Second clone"
print("Second clone: ", second_clone)
if __name__ == "__main__":
main()
PythonOutput:
Output will be as below-
Cell object: Cell: (
row=1,
column=1,
height: 10,
width: 100,
content: None,
background: FFFFFF,
text_color: 000000)
Cell clone(without any change): Cell: (
row=1,
column=1,
height: 10,
width: 100,
content: None,
background: FFFFFF,
text_color: 000000)
Clone object after changing: Cell: (
row=1,
column=2,
height: 10,
width: 100,
content: Clone cell,
background: 008000,
text_color: FFFFFF)
Original cell object: Cell: (
row=1,
column=1,
height: 10,
width: 100,
content: None,
background: FFFFFF,
text_color: 000000)
Second clone: Cell: (
row=2,
column=2,
height: 10,
width: 100,
content: Second clone,
background: FFFFFF,
text_color: 000000)
PlaintextExample #2: Plane Prototype & Cloning
Let’s use the prototype pattern to clone a plane object that has an engine object inside it.
Plane Class
import copy
class Plane:
def __init__(
self,
name: str,
manufacturer: str,
model: str,
length: float,
height: float,
wingspan: float,
seat: float,
engine: "PlaneEngine",
):
self.name = name
self.manufacturer = manufacturer
self.model = model
self.length = length
self.height = height
self.wingspan = wingspan
self.seat = seat
self.engine = engine
# Other methods for plane function
def clone(self):
return copy.deepcopy(self)
def __repr__(self):
return (
f"Plane(name={self.name}, manufacturer={self.manufacturer}, model={self.model}, "
f"length={self.length}, height={self.height}, wingspan={self.wingspan}, "
f"seat={self.seat}, engine={self.engine})"
)
PythonEngine Class
class PlaneEngine:
def __init__(
self, name: str, length: float, weight: float, no_of_blade: int, rpm: int
):
self.name = name
self.length = length
self.weight = weight
self.no_of_blade = no_of_blade
self.rpm = rpm
# Other methods for engine functions
def __str__(self):
return (
f"PlaneEngine(name={self.name}, length={self.length}, weight={self.weight}, "
f"no_of_blade={self.no_of_blade}, rpm={self.rpm})"
)
PythonDemo
# Demo usage
def main():
# Prepare the engine object
plane_engine = PlaneEngine(
name="GE9X", length=220.0, weight=22000.0, no_of_blade=16, rpm=2510
)
# Create a plane object
plane = Plane(
name="Boeing 777",
manufacturer="Boeing",
model="777-200LR",
length=63.7,
height=18.6,
wingspan=64.8,
seat=317,
engine=plane_engine,
)
# Print details of the original plane
print("Original Plane object:", plane)
# Clone the Plane object
clone_plane = plane.clone()
# Print details just after cloning
print("Clone Plane object:", clone_plane)
# Change some values in the cloned plane
clone_plane.model = "777-300ER"
clone_plane.engine.name = "GE10YYYY"
# Print details of the cloned plane after changes
print("Clone Plane object after change:", clone_plane)
# Check the original Plane object to ensure it's unchanged
print("Original Plane object after changes in the clone:", plane)
if __name__ == "__main__":
main()
PythonOutput
Notice, that the changes in the cloned object do not affect the original, and vice versa.
Original Plane object: Plane(name=Boeing 777, manufacturer=Boeing, model=777-200LR, length=63.7, height=18.6, wingspan=64.8, seat=317, engine=PlaneEngine(name=GE9X, length=220.0, weight=22000.0, no_of_blade=16, rpm=2510))
Clone Plane object: Plane(name=Boeing 777, manufacturer=Boeing, model=777-200LR, length=63.7, height=18.6, wingspan=64.8, seat=317, engine=PlaneEngine(name=GE9X, length=220.0, weight=22000.0, no_of_blade=16, rpm=2510))
Clone Plane object after change: Plane(name=Boeing 777, manufacturer=Boeing, model=777-300ER, length=63.7, height=18.6, wingspan=64.8, seat=317, engine=PlaneEngine(name=GE10YYYY, length=220.0, weight=22000.0, no_of_blade=16, rpm=2510))
Original Plane object after changes in the clone: Plane(name=Boeing 777, manufacturer=Boeing, model=777-200LR, length=63.7, height=18.6, wingspan=64.8, seat=317, engine=PlaneEngine(name=GE9X, length=220.0, weight=22000.0, no_of_blade=16, rpm=2510))
PlaintextSource Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check Prototype pattern implementation in other programming languages.