Python Programming from First Principles: Learn to Code, Think Computationally, and Solve Real-World Problems
PDFEPUB
BOOK 000 · CHAPTER 7 · Inheritance

Inheritance

On this pageDefine the Base ClassAdd a Student SubclassAdd a Professor SubclassUse Inherited ClassesReuse an Internal AttributeCoding Challenge: SavingsAccount

Inheritance defines a specialized class from an existing one. The base class supplies reusable behavior; a subclass can add attributes or override methods.

In UML, a solid line with a hollow triangle points from a subclass toward its base class. The compartments list attributes and operations; + marks public members.

User+user_id+name+email+profile()+role()Student+courses+enroll(course_code)+role()Professor+teaches+assign_course(course_code)+role()

Define the Base Class

class User:
    def __init__(self, user_id, name, email):
        self.user_id = str(user_id)
        self.name = name
        self.email = email

    def profile(self):
        return f"{self.name} <{self.email}>"

    def role(self):
        return "user"
Listing 310 — Define the Base Class

Defines User; no visible output.

User stores the common fields and provides profile and role.

Add a Student Subclass

Student(User) names the base class. super() finds the next implementation in Python’s method resolution order; in this hierarchy, super().__init__ reaches User’s initializer.

class Student(User):
    def __init__(self, user_id, name, email):
        super().__init__(user_id, name, email)
        self.courses = []

    def role(self):
        return "student"

    def enroll(self, course_code):
        if course_code not in self.courses:
            self.courses.append(course_code)
Listing 311 — Add a Student Subclass

Defines Student; no visible output.

The same student object receives the common fields and its own courses list. Overriding role gives it a more specific label.

Add a Professor Subclass

class Professor(User):
    def __init__(self, user_id, name, email):
        super().__init__(user_id, name, email)
        self.teaches = []

    def role(self):
        return "professor"

    def assign_course(self, course_code):
        if course_code not in self.teaches:
            self.teaches.append(course_code)
Listing 312 — Add a Professor Subclass

Defines Professor; no visible output.

Professor reuses User’s initialization and adds its own teaching list.

Use Inherited Classes

Run the definitions above, then:

s1 = Student(101, "Anna", "anna@example.com")
p1 = Professor(44, "Dr. Singh", "singh@example.com")

print(s1.profile())
print(p1.profile())

print(s1.role())
print(p1.role())
Listing 313 — Use Inherited Classes

Output

Anna <anna@example.com>
Dr. Singh <singh@example.com>
student
professor

Both objects use the inherited profile method, but their role implementations return different labels.

Reuse an Internal Attribute

The underscore convention was introduced under encapsulation. It also applies when a subclass works with a base class’s internal data.

class Employee:
    def __init__(self, name, salary):
        self.name = name
        self._salary = salary   # protected


class Manager(Employee):
    def give_raise(self, amount):
        self._salary += amount   # allowed: subclass can access protected members
Listing 314 — Reuse an Internal Attribute

Defines Employee, Manager; no visible output.

Manager adds behavior to Employee; it does not introduce enforced access protection.

m = Manager("Alex", 60000)
m.give_raise(5000)
print(m._salary)   # still accessible, but client code should use a public operation
Listing 315 — Reuse an Internal Attribute

Output

65000

The inherited initializer sets 60000; give_raise(5000) changes it to 65000.

Coding Challenge: SavingsAccount

After completing the BankAccount exercise in Encapsulation and running your completed class definition, create a subclass storing an interest rate. Use the inherited balance query and deposit method to add interest; do not bypass them to change the balance.

Exercise starter: complete before running.

class SavingsAccount(BankAccount):
    def __init__(self, initial_balance=0, interest_rate=0.01):
        # call parent constructor and store a private interest rate
        pass

    # add a method to apply interest
Listing 316 — Coding Challenge: SavingsAccount

For the target below, start at 1000, pass interest_rate=0.05, deposit 500 and apply interest once. The starter default of 0.01 would give a different result.

Target output for your solution

Starting balance: 1000
After deposit: 1500
After interest: 1575.0

Explain which methods are inherited and which responsibility the subclass adds.

We can now compare inheritance with composition and decide when a relationship means “is a” or “has a.”