🤖 Robot Factory

Every robot needs a name, a battery, and the ability to charge. Yet a cleaning robot, a delivery robot, and a security robot each perform completely different work. Inheritance lets us capture the common parts once and specialize only what is unique.

Python 3.11+ Hierarchical Inheritance Method Overriding Live Code Runner ~20 min
Back to Inheritance Guide Run the Code

🎯 By the end of this tutorial, you will be able to:

  • Identify when inheritance is the right tool to eliminate code duplication
  • Design a base class that captures shared state and behavior
  • Create subclasses that extend the base class using super()
  • Override methods to provide specialized behavior
  • Use polymorphism to write flexible, reusable code
  • Apply the Open/Closed Principle in practice
  • Recognize common inheritance pitfalls and how to avoid them

What Happens Without Inheritance?

Imagine you need three different robots. Without inheritance, you'd write three nearly identical classes:

❌

Without Inheritance — Duplicated Code

class CleaningBot: def __init__(self, name, battery=100, floor_type="tile"): self.name = name self.battery = battery self.floor_type = floor_type def charge(self): self.battery = 100 def run_task(self): self.battery -= 15 return f"🧹 Cleaning {self.floor_type}" class DeliveryBot: class SecurityBot:

⚠️ The Problem: The __init__, charge(), and status() logic is duplicated in every class. If you need to change the charging logic, you must update it in three places. If you add a new robot type, you copy-paste again. This is brittle, error-prone, and violates the DRY (Don't Repeat Yourself) principle.

💡 The Solution: Use inheritance to extract the shared code into a base class. The subclasses will then inherit the common behavior and only define what makes them unique.

📋 Your Learning Progress

Step 1: The Robot Base Class

We move all shared code into a single Robot class. This class captures the common interface that all robots will share.

1

The Base Robot Class

class Robot: def __init__(self, name: str, battery: int = 100): self.name = name self.battery = battery def charge(self) -> None: self.battery = 100 print(f"⚡ {self.name} charged to 100%") def status(self) -> str: return f"🤖 {self.name}: battery {self.battery}%" def run_task(self) -> str: raise NotImplementedError("Subclasses must implement run_task()")

🔑 Key Points:

  • The base class contains all the common code — once.
  • run_task() raises NotImplementedError to force subclasses to provide their own implementation.
  • This is a lightweight version of an abstract method.
  • Any new robot type can now inherit from Robot and only override run_task().

✅ Result: The duplication is gone. All robots share the same __init__, charge(), and status() logic. If we need to change how charging works, we change it once in the base class, and every robot automatically gets the improvement.

Step 2: Creating the Subclasses

Now we create three subclasses, each of which:

  • Inherits from Robot
  • Calls super().__init__() to initialize the parent
  • Adds its own specialized attribute (e.g., floor_type)
  • Overrides run_task() with its own logic
2

The CleaningBot Subclass

class CleaningBot(Robot): def __init__(self, name, battery=100, floor_type="tile"): super().__init__(name, battery) self.floor_type = floor_type def run_task(self) -> str: if self.battery < 20: return f"🧹 {self.name} needs to charge before cleaning {self.floor_type}." self.battery -= 15 return f"🧹 {self.name} is vacuuming the {self.floor_type} floor. Battery now {self.battery}%"

🔑 Why super().__init__() is essential: If you forget this call, the subclass will not have self.name or self.battery defined. This will cause an AttributeError when you try to use status() or run_task(). Always initialize the parent first!

3

The DeliveryBot and SecurityBot Subclasses

class DeliveryBot(Robot): def __init__(self, name, battery=100, destination="warehouse"): super().__init__(name, battery) self.destination = destination def run_task(self) -> str: if self.battery < 30: return f"📦 {self.name} cannot deliver — battery too low." self.battery -= 25 return f"📦 {self.name} delivered package to {self.destination}. Battery now {self.battery}%" class SecurityBot(Robot): def __init__(self, name, battery=100, patrol_zone="perimeter"): super().__init__(name, battery) self.patrol_zone = patrol_zone def run_task(self) -> str: if self.battery < 10: return f"🔒 {self.name} is too low on battery to patrol." self.battery -= 10 return f"🔒 {self.name} is patrolling the {self.patrol_zone}. Battery now {self.battery}%"
🤖 Robot (Base)
• name, battery
• charge(), status()
• run_task() [abstract]
⬇️ inherits
🧹 CleaningBot
• floor_type
• overrides run_task()
• drain: 15%
📦 DeliveryBot
• destination
• overrides run_task()
• drain: 25%
🔒 SecurityBot
• patrol_zone
• overrides run_task()
• drain: 10%

The Full Robot Factory

Here is the complete implementation. Click Run to execute it in your browser — or Copy to paste into your own environment.

robots.py
class Robot:
    def __init__(self, name: str, battery: int = 100):
        self.name = name
        self.battery = battery

    def charge(self) -> None:
        self.battery = 100
        print(f"⚡ {self.name} charged to 100%")

    def status(self) -> str:
        return f"🤖 {self.name}: battery {self.battery}%"

    def run_task(self) -> str:
        raise NotImplementedError("Subclasses must implement run_task()")

class CleaningBot(Robot):
    def __init__(self, name: str, battery: int = 100, floor_type: str = "tile"):
        super().__init__(name, battery)
        self.floor_type = floor_type

    def run_task(self) -> str:
        if self.battery < 20:
            return f"🧹 {self.name} needs to charge before cleaning {self.floor_type}."
        self.battery -= 15
        return f"🧹 {self.name} is vacuuming the {self.floor_type} floor. Battery now {self.battery}%"

class DeliveryBot(Robot):
    def __init__(self, name: str, battery: int = 100, destination: str = "warehouse"):
        super().__init__(name, battery)
        self.destination = destination

    def run_task(self) -> str:
        if self.battery < 30:
            return f"📦 {self.name} cannot deliver — battery too low."
        self.battery -= 25
        return f"📦 {self.name} delivered package to {self.destination}. Battery now {self.battery}%"

class SecurityBot(Robot):
    def __init__(self, name: str, battery: int = 100, patrol_zone: str = "perimeter"):
        super().__init__(name, battery)
        self.patrol_zone = patrol_zone

    def run_task(self) -> str:
        if self.battery < 10:
            return f"🔒 {self.name} is too low on battery to patrol."
        self.battery -= 10
        return f"🔒 {self.name} is patrolling the {self.patrol_zone}. Battery now {self.battery}%"

cleaner = CleaningBot("Dusty", floor_type="carpet")
delivery = DeliveryBot("Speedy", destination="downtown")
security = SecurityBot("Guardian", patrol_zone="building B")

robots = [cleaner, delivery, security]

for bot in robots:
    print(bot.status())
    print(bot.run_task())
    print("---")

print("\n🔋 After a full charge:")
cleaner.charge()
print(cleaner.run_task())

💡 Try it yourself: Change the battery thresholds, invent a new robot type, or add a second specialized method. Because the code runs in the browser (via Pyodide), you can experiment instantly and see the results without leaving the page.

What You'll See

When you click Run, the program creates three robots, asks each for its status, then asks each to perform its task.

🤖 Dusty: battery 100% 🧹 Dusty is vacuuming the carpet floor. Battery now 85% --- 🤖 Speedy: battery 100% 📦 Speedy delivered package to downtown. Battery now 75% --- 🤖 Guardian: battery 100% 🔒 Guardian is patrolling the building B. Battery now 90% --- 🔋 After a full charge: ⚡ Dusty charged to 100% 🧹 Dusty is vacuuming the carpet floor. Battery now 85%

🔍 What to notice:

  • Each robot reports the same style of status line because status() lives in the base class.
  • Each robot produces a different task message and drains a different amount of battery because run_task() was overridden.
  • The final two lines prove that a method defined only in the parent (charge()) is fully available on a child instance — no extra code required.
  • This is polymorphism in action: the same call (run_task()) produces different results depending on the actual type of the robot.

The Core Ideas Illustrated by the Robot Factory

This project is deliberately small so the important ideas stand out clearly. Below is what each part of the design is teaching you.

🧬
Single Inheritance

Each subclass has exactly one parent. Keeps the hierarchy simple and predictable.

🔗
super()

Always call the parent constructor. Forgetting this is a common bug.

🔄
Method Overriding

Subclasses replace or specialize behavior while reusing everything else.

🎭
Polymorphism

Same call, different behavior. Write code that works with any robot.

🔓
Open/Closed

Open for extension, closed for modification. Add new robots without changing existing code.

🚫
NotImplementedError

A lightweight way to enforce that subclasses implement required methods.

1. Single Inheritance

class CleaningBot(Robot):

Every subclass has exactly one parent. This keeps the hierarchy simple and predictable. Python also supports multiple inheritance, but single inheritance is the foundation you should master first.

2. Calling the Parent Constructor with super()

super().__init__(name, battery)

The subclass is responsible for initializing the parts it inherits. super() finds the correct parent class and calls its __init__. After that call finishes, the subclass adds its own attributes (floor_type, destination, patrol_zone). This two-step initialization is the standard pattern.

3. Method Overriding

Each robot redefines only run_task(). The methods status() and charge() are never rewritten; they are simply inherited. Overriding lets a subclass replace or specialize behavior while still reusing everything else.

4. Polymorphism

for bot in robots: print(bot.run_task())

The loop does not care whether the object is a CleaningBot, DeliveryBot, or SecurityBot. It only knows that every object supports the same interface (status and run_task). This is polymorphism: the same call produces different results depending on the actual type of the object.

5. The Open/Closed Principle

The base Robot class is closed for modification (we do not need to change it when we invent a new robot). It is open for extension (we simply write a new subclass). Adding a ChefBot or a FirefighterBot requires zero changes to existing code.

6. Abstract-like Behavior with NotImplementedError

def run_task(self) -> str: raise NotImplementedError(...)

This is a lightweight way to say "every real robot must define its task." In larger projects you would often use abc.ABC and @abstractmethod, but the principle is the same.

Inheritance vs Composition

A common question: When should I use inheritance, and when should I use composition? Here's a quick comparison:

Inheritance (is-a)Composition (has-a)
"A CleaningBot is a Robot""A Robot has a BatteryManager"
Use when you want to reuse behavior from a parent classUse when you want to delegate behavior to another object
Changes to the base class affect all subclassesChanges to the component affect only the class that uses it
Can lead to deep, rigid hierarchies if overusedMore flexible and easier to test
Prefer when: The relationship is truly "is-a" and the subclass is a clear subtypePrefer when: The relationship is "has-a" and you want to avoid tight coupling

🧠 Mental Model to Keep: The base class is a contract + shared toolbox. Subclasses accept the contract and only customize what makes them unique. Once this model is solid, the rest of object-oriented design (abstract base classes, mixins, composition over inheritance, dependency injection) becomes much easier to learn.

Common Mistakes to Avoid

MistakeBetter Approach
Copy-pasting the same methods into every subclassPut shared logic in the base class
Forgetting to call super().__init__()Always initialize the parent first
Making the base class know about every possible subclassKeep the base class general; let subclasses specialize
Overriding methods just to change a tiny detailPrefer composition or parameters when the difference is small
Deep inheritance trees (5+ levels)Prefer shallow hierarchies + composition

Real-World Connections

🤖
ROS Nodes
Robot Operating System uses inheritance for device drivers
🌐
Django Models
Model inheritance in Django ORM
🎮
Game Characters
Pygame sprite hierarchies
🖥️
GUI Widgets
Qt, Tkinter, and web components

Reinforce Your Learning

Try these exercises to solidify your understanding of inheritance. Each one builds on the concepts you've just learned.

🧪 Exercise 1: Add a ChefBot

Create a new robot that follows the same contract. A ChefBot should have a specialty attribute and a run_task() that cooks a dish. It should drain 20% battery per cooking session and require at least 25% battery to start.

Your ChefBot should inherit from Robot, call super().__init__(), and override run_task() with the logic above. Make sure to check battery before draining!

🧪 Exercise 2: Fix the Broken Robot

This code is broken — it will cause an AttributeError. Can you fix it?

Add super().__init__(name, battery=100) to the __init__ method. This initializes the parent class, giving the instance self.name and self.battery.

class FixedBot(Robot):
    def __init__(self, name: str, battery: int = 100):
        super().__init__(name, battery)

🧪 Exercise 3: Predict the Output

What will this code print when run? Try to work it out in your head, then click "Show Result" to check.

class TestBot(Robot): def run_task(self) -> str: self.battery -= 5 return f"Testing... battery now {self.battery}%" t = TestBot("Tester", 50) print(t.status()) print(t.run_task()) print(t.status()) t.charge() print(t.status())
🤖 Tester: battery 50%
Testing... battery now 45%
🤖 Tester: battery 45%
⚡ Tester charged to 100%
🤖 Tester: battery 100%
                        

Notice that status() and charge() are inherited from Robot — no extra code needed!

📝 Test Your Understanding

Question 1: What does super().__init__() do?

Question 2: Why does the base Robot class raise NotImplementedError in run_task()?

Question 3: Which methods are inherited (not overridden) in the CleaningBot class?

Question 4: What concept is demonstrated by for bot in robots: bot.run_task()?

Question 5: What is the Open/Closed Principle?

Question 6: Which is better — inheritance or composition?