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:
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:
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:
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__:
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:
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.
In class SearchTool(Tool):, what does putting Tool in parentheses do?
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.