Inheritance

Basic syntax, compared to what you know

Inheritance lets one class build on another — same core idea as Java’s extends, C++’s : public Base, or JS’s extends, with Python’s own spelling:

Try it — edit and run

SearchTool(Tool) — the parent class goes in parentheses after the class name. SearchTool doesn’t define anything of its own yet, so it inherits __init__ and run from Tool entirely unchanged; pass is just a placeholder body since a class can’t be empty. Tool is the base class (or superclass/parent class); SearchTool is the subclass (or derived class/child class).

super().__init__() — extending the parent constructor, not replacing it

A subclass usually needs its own extra data on top of what the parent already sets up. Redefining __init__ from scratch would mean duplicating everything the parent already does:

class SearchTool(Tool):
    def __init__(self, name: str, max_results: int):
        self.name = name              # duplicating what Tool.__init__ already does
        self.max_results = max_results

(not run live — illustrating the problem, not a working example)

super() gives you a reference to the parent class, so you can call its __init__ directly instead of repeating its logic:

Try it — edit and run

super().__init__(name) calls Tool.__init__ on the current instance, handling self.name = name exactly as it did before. SearchTool.__init__ then only has to deal with what’s actually new — max_results — instead of re-implementing what the parent already handles.

Overriding methods — and extending vs. replacing

A subclass can redefine a method it inherited, replacing the parent’s version entirely for that subclass:

Try it — edit and run

Each subclass’s own run is what actually executes, even though the loop just calls tool.run(...) the same way for every item — Python looks up the method on the specific object’s actual class first, not the variable’s declared type (there isn’t one, per Lesson 1’s dynamic typing). CalculatorTool didn’t override __init__ at all, so it still uses Tool’s directly — overriding is per-method, not all-or-nothing for the whole class.

Sometimes you want to extend a parent method rather than fully replace it — run the parent’s version, then add more on top. super() works for any method, not just __init__:

Try it — edit and run

isinstance() and the is-a relationship

Inheritance means a SearchTool genuinely is a Tool, not just a class that happens to share some methods — isinstance() checks this relationship directly, and it recognizes the whole hierarchy, not just the exact class:

Try it — edit and run

This distinction matters in practice: code that expects “any Tool” should check with isinstance(x, Tool), which correctly accepts SearchTool, CalculatorTool, or any other subclass — type(x) == Tool would incorrectly reject all of them, since none of them are exactly Tool. This is also exactly why the loop two sections back worked without checking each item’s specific class first: every item in tools is-a Tool, so treating them uniformly and letting each one’s own run execute is safe by design — this is called polymorphism, and it’s the same mechanism tool-calling frameworks rely on to dispatch a call to whichever specific tool matches, without needing a different code path per tool type.

When inheritance is the right call — and when it isn't

Inheritance fits when the relationship is genuinely “is-a”: a SearchTool is-a Tool, a SavingsAccount is-an Account. It’s the wrong tool when the relationship is really “has-a” or “uses-a” — forcing that into inheritance tends to produce a subclass that only wants one or two of its parent’s methods and awkwardly ignores or breaks the rest.

Recall the closure-vs-class comparison from earlier: a closure was a lightweight alternative to a class, for a single piece of remembered state and one operation. Inheritance sits at the opposite end of that same spectrum — reach for it specifically when you have a real hierarchy of related types that should share behavior and be substitutable for each other (like the tools list above), not merely because two classes happen to have a few similarly-named methods.

Check your understanding
1/7

In class SearchTool(Tool):, what does putting Tool in parentheses do?

Exercise · Graded

Implement Tool with __init__, run, and __repr__ as described. Then implement SearchTool inheriting from Tool, calling super().__init__() to handle name, adding its own max_results, and overriding run() — but leaving __repr__ inherited, unchanged, from Tool.