Design Pattern: Prototype Pattern in Python

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

Add a method named “clone” (or any name you prefer).
Import package “copy“.
Return the “copy.copy(self)” from the “clone” method.
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)
Python

Output:

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)
Plaintext

Method #2: Abstract Prototype

This is an improvement over the last discussed method-

Create a prototype class, here we have named it “Prototype“.
Define the method “clone” in the “Prototype” class. Return “copy.copy(self)” from the “clone” method.
Inherit the “Prototype” class in your class.

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)
Python

Output:

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)
Plaintext

Method #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.

Prototype Shallow Copy
Prototype Shallow Copy

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)
Python

Output:

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)) 
Plaintext

Solve 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()“.

Prototype Deep Copy
Prototype Deep 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)
Python

Output:

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))
Plaintext

Examples

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

Create class named “CellPrototype“.
Define the “clone” method in the “CellProrotype” class. It should return “copy.deepcopy(self)“. Might perform any additional operations.
Define “Cell” class and inherit from “CellPrortotype“.
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})"""
Python

Demo

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()
Python

Output:

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)
Plaintext

Example #2: Plane Prototype & Cloning

Let’s use the prototype pattern to clone a plane object that has an engine object inside it.

Plane Class

Create a class named “Plane“.
Initialize required attributes in the “__init__” method.
Define “clone” method and return “copy.deepcopy(self)
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})"
        )
Python

Engine Class

Define “PlangeEngine” class.
Define required attributes – name, length, weight, no_ob_blade, rpm.
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})"
        )
Python

Demo

Create a “PlaneEngine” object.
Create “Plane” object and pass the “PlaneEngine” object to it.
We can use the existing plane object method “clone” to create new Plane objects.
# 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()
Python

Output

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))
Plaintext

Source 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.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.