Inheritance and super: Similar Classes Without Duplicated Code

Lesson goals
  • Explain when inheritance saves duplicated code, and check it with the “is a kind of” test
  • Define a Parent and a Child, and understand how Python looks for a method — first in the Child and then in the Parent
  • Override a method in the Child to change behavior
  • Use super() — both in __init__ and to extend a method of the Parent

In this lesson

This lesson is built as one story, in the same order as the slides:

  1. Chapter 1 — why inheritance: the same code twice, and how to inherit instead of copying
  2. Chapter 2 — overriding and super(): inheritance, but not exactly — changing only part of the behavior

At the end of the lesson — practice, and a mini project that connects both chapters.

Chapter 1: Why Inheritance — Parent, Child and the Is-a-Kind-of Relationship

What will we learn?

  • Why inheritance is needed: the problem of almost identical Classes.
  • Parent Class (Base Class) and Child Class (Subclass).
  • The syntax of inheritance in Python.
  • What is passed on through inheritance.
  • The is-a test: when inheritance suits and when it does not.

Why do we need this?

In the lesson on classes and objects we asked: what happens when you add a “savings account” that is similar to a regular account, but with interest? In the procedural version, every function filled up with if account["type"] == .... Let’s see the same problem in OOP, without inheritance:

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

    def monthly_cost(self):
        return self.salary * 1.25

    def describe(self):
        return self.name + " earns " + str(self.salary)

class Manager:
    def __init__(self, name, salary, team_size):
        self.name = name
        self.salary = salary
        self.team_size = team_size

    def monthly_cost(self):
        return self.salary * 1.25

    def describe(self):
        return self.name + " earns " + str(self.salary)

    def team_report(self):
        return self.name + " manages " + str(self.team_size) + " people"

Manager copies almost all of Employee: name, salary, monthly_cost, describe. What happens when the employer cost changes from 1.25 to 1.3? You have to fix it in two places, and if there are also Intern, Contractor and Director, in five. This is a violation of DRY at the level of Classes.

And one more thing: a manager is an employee. Everything that is true for an employee is also true for a manager, and in addition the manager has more things. The code should reflect this relationship.

Explanation from the basics

Inheritance

Inheritance = a mechanism in which a new Class automatically receives all the Attributes and methods of an existing Class, and can add or change parts.

  • Parent Class (Base, Superclass) — the general Class. Employee.
  • Child Class (Derived, Subclass) — the specific Class, which inherits. Manager.
Analogy: a car production template — the sports version

We have a production template of a “car” (the Parent): 4 wheels, an engine, steering, braking. Now we want a “sports car”. We don’t design a car from scratch. We take the existing template, and add a spoiler and change the engine. Every sports car is a car (it drives, it brakes), but not every car is a sports car.

The “is a kind of” relationship (is-a)

Inheritance suits only when you can honestly say: “the Child is a kind of the Parent”.

SentenceInheritance?
A manager is a kind of employee✓ Manager(Employee)
A dog is a kind of animal✓ Dog(Animal)
A savings account is a kind of bank account✓ SavingsAccount(BankAccount)
A car is a kind of engine?✗ A car has an engine (has-a), and that is Composition, the OOP design topic
An order is a kind of customer?✗ An order has a customer

What is passed on through inheritance?

The Child receives everything from the Parent:

  • The __init__ (if the Child does not define an __init__ of its own).
  • All the methods.
  • The Class Attributes.

And the Child can:

  • Add new methods and Attributes.
  • Change (Override) existing methods. We will learn it in the next chapter, together with super().

Lookup in layers — again

When you write manager.describe(), Python looks: (1) on the object, (2) in its Class (Manager), (3) in the Parent (Employee)… and stops at the first place where it found it. This is the same idea of a layered lookup that we saw in LEGB and in the lookup order of Attributes.

Syntax

class Parent:
    def __init__(self, x):
        self.x = x

    def shared_method(self):
        return ...

class Child(Parent):             # Child inherits from Parent
    def extra_method(self):      # new method, only in Child
        return ...

c = Child(5)                     # uses Parent.__init__ (Child has none of its own)
c.shared_method()                # inherited
c.extra_method()                 # Child's own
  • The Parent’s name comes in brackets after the Child’s name.
  • A Child without any addition: class Child(Parent): pass is a valid Class that is identical to the Parent.

First code example

week-3/employees.py:

class Employee:
    EMPLOYER_COST_RATE = 1.25

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def monthly_cost(self):
        return self.salary * Employee.EMPLOYER_COST_RATE

    def describe(self):
        return self.name + " earns " + str(self.salary)

class Manager(Employee):
    def team_report(self, team_size):
        return self.name + " manages " + str(team_size) + " people"

dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)

print(dana.describe())
print(avi.describe())
print(avi.monthly_cost())
print(avi.team_report(5))

Output:

Dana earns 12000
Avi earns 20000
25000.0
Avi manages 5 people

Breaking down the code line by line

CodeExplanation
class Manager(Employee):Manager inherits from Employee.
Manager("Avi", 20000)Manager has no __init__ of its own, so Python finds the one of Employee and uses it. Avi gets a name and a salary.
avi.describe()There is no describe in Manager. Python goes up to Employee and finds it. self is still Avi.
avi.monthly_cost()The same, and EMPLOYER_COST_RATE is also found through the Parent.
avi.team_report(5)A method that exists only in Manager.
dana.team_report(...)Would throw an AttributeError: a regular employee does not inherit from the manager. Inheritance goes from top to bottom only.

And the fix of 1.25 → 1.3? You change one line, in Employee, and all the managers (and in the future, every kind of employee) are updated.

Another example

A hierarchy with several children: Animal / Dog / Cat. week-3/animals.py:

class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def birthday(self):
        self.age = self.age + 1

    def describe(self):
        return self.name + " (" + str(self.age) + " years old)"

class Dog(Animal):
    def fetch(self):
        return self.name + " brings back the ball"

class Cat(Animal):
    def climb(self):
        return self.name + " climbs the tree"

rex = Dog("Rex", 3)
mitzi = Cat("Mitzi", 5)

rex.birthday()
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())

for animal in [rex, mitzi]:
    print(type(animal).__name__, "->", animal.describe())

Output:

Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the tree
Dog -> Rex (4 years old)
Cat -> Mitzi (5 years old)

Breakdown:

  • Two children of the same Parent: Dog and Cat share everything that is common (name, age, birthday, describe), and each one adds what is unique to it.
  • rex.birthday() — a method written once in Animal, which changes rex’s State.
  • In the loop, animal.describe() works on every kind of Animal, without checking which kind it is. This is a hint of Polymorphism, which we will go deeper into in the next lesson.
  • type(animal).__name__ — the Class’s name as text.
         Animal
        /      \
      Dog      Cat

Summary

  • Inheritance = a Child Class receives all the Attributes and methods of a Parent Class, and can add and change.
  • Syntax: class Child(Parent):.
  • It solves duplication between similar Classes (DRY), and reflects the “is a kind of” relationship (is-a).
  • Python looks for a method in the object’s Class, and if it did not find it, goes up to the Parent.
  • Inheritance is one-directional: the Parent does not know what was added in the Child.
  • Do not inherit when the relationship is “has a” (has-a). Then you use Composition (the OOP design topic).

Official documentation

Chapter 2: Overriding and super() — Changing Only What Is Different

What will we learn?

  • super(): how a Child uses the Parent’s code.
  • __init__ in a Child: adding Attributes without copying the Parent’s.
  • Method Overriding: a Child replaces a method of the Parent.
  • Extending: a Child adds to the Parent’s method, instead of replacing it completely.

Why do we need this?

This is where plain inheritance gets stuck: a Truck that inherits from a Vehicle needs an extra Attribute (cargo_kg), meaning an __init__ of its own. But if we write a new __init__ in Truck, it replaces the one of Vehicle, and then brand and wheels will not be initialized, unless we copy the code. And that is exactly what inheritance came to prevent.

And a second problem: sometimes the Child needs to behave differently in the same action. A manager gets a bonus in the cost calculation. A savings account does not allow withdrawing below a minimum. We need a way to change a method that came through inheritance.

Explanation from the basics

Method Overriding

If in the Child you define a method with the same name as in the Parent, the Child’s version wins for objects of type Child. This is called an Override. Why? Remember the lookup order: Python looks first in the object’s Class, and only if it did not find it, goes up to the Parent. If the method exists in the Child, Python never even reaches the Parent’s version.

super() — “the Parent’s version”

super() returns a special object through which you call the Parent’s method, on the same self. The two main uses:

  1. In __init__: super().__init__(...) — “first initialize everything the Parent initializes”, and then add what is unique to the Child.
  2. In extending a method: super().describe() — “bring me what the Parent returns”, and then add to it.
Analogy: a family recipe

Grandma (the Parent) bakes a chocolate cake from a recipe. You (the Child) inherit the recipe. Override = you decide to bake a completely different cake with the same name, “chocolate cake”, but vegan, from scratch. Extending with super() = “do exactly like grandma” (super().bake()), and then add a frosting you invented. You don’t copy grandma’s whole recipe. You simply say “like grandma, plus…”.

Three options for a method in a Child

OptionWhat you doWhen
InheritYou write nothingThe Parent’s behavior suits
OverrideYou write a method with the same name, without super()The behavior is completely different
ExtendYou write a method with the same name, and inside it super().method()“Like the Parent, plus something”

Syntax

class Parent:
    def __init__(self, a):
        self.a = a

    def describe(self):
        return "a=" + str(self.a)

    def greet(self):
        return "Hello from Parent"

class Child(Parent):
    def __init__(self, a, b):
        super().__init__(a)          # let Parent set up self.a
        self.b = b                   # then add what is new in Child

    def describe(self):              # EXTEND: reuse Parent's result
        return super().describe() + ", b=" + str(self.b)

    def greet(self):                 # OVERRIDE: replace completely
        return "Hello from Child"

Notice: in super().__init__(a) you do not pass self. super() already “knows” which object it is about.

First code example

Solving the Truck problem. week-3/vehicles_super.py:

class Vehicle:
    def __init__(self, brand, wheels):
        self.brand = brand
        self.wheels = wheels

    def describe(self):
        return self.brand + " with " + str(self.wheels) + " wheels"

class Truck(Vehicle):
    def __init__(self, brand, max_load_kg):
        super().__init__(brand, 6)
        self.max_load_kg = max_load_kg
        self.cargo_kg = 0

    def load(self, kg):
        if self.cargo_kg + kg > self.max_load_kg:
            raise ValueError("over the limit: max " + str(self.max_load_kg) + " kg")
        self.cargo_kg = self.cargo_kg + kg

    def describe(self):
        return super().describe() + ", carrying " + str(self.cargo_kg) + "/" + str(self.max_load_kg) + " kg"

volvo = Truck("Volvo", 1000)
volvo.load(600)
print(volvo.describe())

try:
    volvo.load(500)
except ValueError as error:
    print("Rejected:", error)

print(volvo.brand, volvo.wheels)

Output:

Volvo with 6 wheels, carrying 600/1000 kg
Rejected: over the limit: max 1000 kg
Volvo 6

Breaking down the code line by line

CodeExplanation
def __init__(self, brand, max_load_kg):Truck has an __init__ of its own, with parameters that suit a truck.
super().__init__(brand, 6)“Run Vehicle’s __init__ on this object”. This creates self.brand and self.wheels. A truck always has 6 wheels, so the Child decides the value.
self.max_load_kg = ..., self.cargo_kg = 0Only what is new in Truck. Without copying a single line from Vehicle.
def describe(self): in TruckExtending: the same name as in Vehicle.
super().describe()Gets "Volvo with 6 wheels" from the Parent, and adds the cargo details to it.
volvo.brand, volvo.wheelsThey exist, because super().__init__ created them.

If Vehicle changes tomorrow, for example adds self.year, trucks will get it automatically through super().__init__. If we had copied the code, we would need to remember to update Truck too.

Another example

A full Override, extending, and super() inside business rules. week-3/staff.py:

class Employee:
    EMPLOYER_COST_RATE = 1.25

    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def monthly_cost(self):
        return self.salary * Employee.EMPLOYER_COST_RATE

    def title(self):
        return "Employee"

    def describe(self):
        return self.title() + " " + self.name + " - cost " + str(self.monthly_cost())

class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus

    def monthly_cost(self):
        return super().monthly_cost() + self.bonus

    def title(self):
        return "Manager"

class Intern(Employee):
    def monthly_cost(self):
        return self.salary

    def title(self):
        return "Intern"

staff = [Employee("Dana", 12000), Manager("Avi", 20000, 3000), Intern("Noa", 5000)]

for person in staff:
    print(person.describe())

print("Total monthly cost:", sum(person.monthly_cost() for person in staff))

Output:

Employee Dana - cost 15000.0
Manager Avi - cost 28000.0
Intern Noa - cost 5000
Total monthly cost: 48000.0

Breakdown:

  • Manager.monthly_cost — extending: “like a regular employee (super().monthly_cost() = 25,000), plus a bonus”. If the employer cost rate changes, the managers will get the change automatically.
  • Intern.monthly_cost — a full Override: an intern does not carry employer costs, so a completely different calculation, without super().
  • title() — every Class overrides it.
  • The interesting point: describe() is written only once, in Employee. It calls self.title() and self.monthly_cost(). When the object is a Manager, self.monthly_cost() reaches the Manager’s version! The Parent’s method automatically uses the Child’s overridden versions. This is the basis of Polymorphism (the next lesson).
  • Intern did not define an __init__, so it uses the one of Employee (regular inheritance).

Summary

  • Override = a method in the Child with the same name as in the Parent. The Child’s version wins.
  • super() = access to the Parent’s version, on the same self.
  • A Child with an __init__ of its own must call super().__init__(...) (without self), and then add only what is new.
  • Extending: super().method() + an addition. A full override: without super().
  • self.method() inside an overridden method = recursion. For the Parent’s version: super().method().
  • A Parent’s method that calls self.x() will automatically reach the Child’s overridden version.
  • An overridden method keeps the same signature (the same parameters).

Official documentation

Practice

The exercises use only what was taught in this lesson and the lessons before it. Try on your own first, and only then open the solution.

What you practice on this page: inheritance — Parent and Child, the “is a kind of” test, extending __init__ with super(), and overriding a method (Override).

By the end of the page you will know how to:

  • Write a Child that inherits from a Parent and adds only what is new
  • Decide when inheritance suits, using the “is a kind of” test
  • Call super().__init__ so that the Parent initializes its part
  • Override a method, and use the Parent’s version inside it

Exercise 1: An employee and a manager

What you build: an employee and a manager — where a manager is a kind of employee, so you don’t write again what is shared.

What to write:

  1. A Class named Employee with a name and a salary.
  2. In Employee: describe that returns the name and the salary, as in the output.
  3. A Class named Manager that inherits from Employee — without an __init__ of its own.
  4. In Manager only: manage that receives a team size and returns a sentence as in the output.

Test code:

dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.manage(5))

The desired output:

Dana: 12000
Avi: 20000
Avi manages 5 people
Show solution
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def describe(self):
        return self.name + ": " + str(self.salary)


class Manager(Employee):
    def manage(self, team_size):
        return self.name + " manages " + str(team_size) + " people"


dana = Employee("Dana", 12000)
avi = Manager("Avi", 20000)
print(dana.describe())
print(avi.describe())
print(avi.manage(5))
Dana: 12000
Avi: 20000
Avi manages 5 people

Manager inherits __init__ and describe — without writing them again — and adds only what is new.

Exercise 2: is-a — is a kind of

What you do: decide — without code — when inheritance suits.

The task: for each pair, answer: should the first inherit from the second? Explain with the sentence “X is a kind of Y” — and if the sentence is not true, what the relationship between them actually is.

  1. Dog and Animal
  2. Car and Engine
  3. SavingsAccount and BankAccount
  4. Square and Color

What to submit: yes or no for each pair, and one sentence of explanation.

Show solution
  1. Yes — a dog is a kind of animal.
  2. No — a car is not a kind of engine. A car has an engine (has-a). This is a case for composition, not for inheritance.
  3. Yes — a savings account is a kind of bank account.
  4. No — a square is not a kind of color. Maybe a square has a color.

Inheritance only when the sentence “X is a kind of Y” is true.

Exercise 3: An animal tree

What you build: a small inheritance tree — an animal, and two kinds of animals, each with an action of its own.

What to write:

  1. A Class named Animal with a name and an age, and describe that returns the name and the age as in the output.
  2. A Class named Dog that inherits from Animal, with fetch that returns the sentence from the output.
  3. A Class named Cat that inherits from Animal, with climb that returns the sentence from the output.

Test code:

rex = Dog("Rex", 4)
mitzi = Cat("Mitzi", 5)
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())

The desired output:

Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the tree
Show solution
class Animal:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def describe(self):
        return self.name + " (" + str(self.age) + " years old)"


class Dog(Animal):
    def fetch(self):
        return self.name + " brings back the ball"


class Cat(Animal):
    def climb(self):
        return self.name + " climbs the tree"


rex = Dog("Rex", 4)
mitzi = Cat("Mitzi", 5)
print(rex.describe(), "|", rex.fetch())
print(mitzi.describe(), "|", mitzi.climb())
Rex (4 years old) | Rex brings back the ball
Mitzi (5 years old) | Mitzi climbs the tree

Exercise 4: super().init

What you build: a truck — a vehicle that always has 6 wheels, and also has a maximum cargo weight.

What to write:

  1. A Class named Vehicle with a brand and a number of wheels.
  2. A Class named Truck that inherits from Vehicle. Its __init__ receives a brand and a maximum weight.
  3. Inside the __init__ of Truck: call super().__init__ with the brand and 6 wheels, and initialize a current weight of 0.
  4. load — receives kilograms and adds them to the weight. If the maximum is exceeded: a ValueError with the message from the output.

Test code:

truck = Truck("Volvo", 1000)
truck.load(600)
print(truck.brand, truck.wheels, truck.load_kg)
try:
    truck.load(500)
except ValueError as error:
    print("error:", error)

The desired output:

Volvo 6 600
error: overloaded: max 1000 kg
Show solution
class Vehicle:
    def __init__(self, brand, wheels):
        self.brand = brand
        self.wheels = wheels


class Truck(Vehicle):
    def __init__(self, brand, max_load_kg):
        super().__init__(brand, 6)
        self.max_load_kg = max_load_kg
        self.load_kg = 0

    def load(self, kg):
        if self.load_kg + kg > self.max_load_kg:
            raise ValueError("overloaded: max " + str(self.max_load_kg) + " kg")
        self.load_kg = self.load_kg + kg


truck = Truck("Volvo", 1000)
truck.load(600)
print(truck.brand, truck.wheels, truck.load_kg)
try:
    truck.load(500)
except ValueError as error:
    print("error:", error)
Volvo 6 600
error: overloaded: max 1000 kg

super().__init__(brand, 6) initializes the Parent’s part. After that the Child adds its own Attributes.

Exercise 5: Override with super

What you build: a manager whose monthly cost is the salary plus a bonus.

What to write:

  1. A Class named Employee with a name and a salary, and monthly_cost that returns the salary.
  2. A Class named Manager that inherits from Employee, and also receives a bonus. In its __init__, call super().__init__.
  3. In Manager, override monthly_cost: the Parent’s version, through super(), plus the bonus.

Test code:

print(Employee("Dana", 12000).monthly_cost())
print(Manager("Avi", 20000, 3000).monthly_cost())

The desired output:

12000
23000
Show solution
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def monthly_cost(self):
        return self.salary


class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus

    def monthly_cost(self):
        return super().monthly_cost() + self.bonus


print(Employee("Dana", 12000).monthly_cost())
print(Manager("Avi", 20000, 3000).monthly_cost())
12000
23000

super().monthly_cost() calls the Parent’s version. If the calculation of the base salary changes, Manager will get the change automatically.

Mini project: Employees in a company

Which system you build: an employee system for a company. The company has regular employees, managers and interns. All of them are employees — they have a name and a salary — but each kind is displayed differently, and a manager also has a bonus. The system displays every employee and calculates the monthly cost of all of them together.

What you will learn from the project:

  • Build a Parent with everything that is shared, and Children that add only what is different
  • Call super().__init__ from the Child
  • Override a method, and extend a method with super()
  • See how a method written once in the Parent works correctly for every kind

What to build — step by step:

Step 1 — an employee (Employee). __init__ receives a name and a salary. Three methods:

  • title — returns the word Employee;
  • monthly_cost — returns the salary;
  • describe — returns the name, the role and the monthly cost, as in the output. It uses title and monthly_cost.

Step 2 — a manager (Manager). Inherits from Employee and also receives a bonus. In __init__, call super().__init__ and save the bonus. Override title — the word Manager. Extend monthly_cost: the result of super() plus the bonus.

Step 3 — an intern (Intern). Inherits from Employee and also receives a number of internship months. In __init__, check that the number is between 1 and 12 — otherwise ValueError — and then call super().__init__. Override title — the word Intern. Do not write monthly_cost and describe: they come through inheritance.

The error message — exactly like this:

months must be 1-12

Test code:

staff = [
    Employee("Dana", 12000),
    Manager("Avi", 20000, 3000),
    Intern("Noa", 6000, 6),
]

for person in staff:
    print(person.describe())

try:
    Intern("Tom", 5000, 0)
except ValueError as error:
    print("error:", error)

total = 0
for person in staff:
    total = total + person.monthly_cost()
print("Total monthly cost:", total)

The desired output:

Dana (Employee): 12000
Avi (Manager): 23000
Noa (Intern): 6000
error: months must be 1-12
Total monthly cost: 41000

By the end of the project you will know how to:

  • Design a hierarchy: what goes up to the Parent and what stays in the Child
  • Tell apart three options for a method in a Child: inherit, override, or extend with super()
  • Understand why describe shows every employee their own role and cost, even though it was written once

Bonus: add to Manager a method manage that receives a team size and returns a sentence like Avi manages 5 people, and print it only for the managers in the list.

Show solution
class Employee:
    def __init__(self, name, salary):
        self.name = name
        self.salary = salary

    def title(self):
        return "Employee"

    def monthly_cost(self):
        return self.salary

    def describe(self):
        return self.name + " (" + self.title() + "): " + str(self.monthly_cost())


class Manager(Employee):
    def __init__(self, name, salary, bonus):
        super().__init__(name, salary)
        self.bonus = bonus

    def title(self):
        return "Manager"

    def monthly_cost(self):
        return super().monthly_cost() + self.bonus


class Intern(Employee):
    def __init__(self, name, salary, months):
        if months < 1 or months > 12:
            raise ValueError("months must be 1-12")
        super().__init__(name, salary)
        self.months = months

    def title(self):
        return "Intern"


staff = [
    Employee("Dana", 12000),
    Manager("Avi", 20000, 3000),
    Intern("Noa", 6000, 6),
]

for person in staff:
    print(person.describe())

try:
    Intern("Tom", 5000, 0)
except ValueError as error:
    print("error:", error)

total = 0
for person in staff:
    total = total + person.monthly_cost()
print("Total monthly cost:", total)
Dana (Employee): 12000
Avi (Manager): 23000
Noa (Intern): 6000
error: months must be 1-12
Total monthly cost: 41000
  • describe was written once in Employee, but every employee gets their own title() and monthly_cost() — because Python looks for the method first in the Child.
  • Manager.monthly_cost extends: super().monthly_cost() returns the salary, and the bonus is added. title overrides completely.
  • Intern does not write monthly_cost — it inherits it as it is.

Bonus:

    def manage(self, team_size):
        return self.name + " manages " + str(team_size) + " people"

(A method inside Manager.)

for person in staff:
    if person.title() == "Manager":
        print(person.manage(5))
Avi manages 5 people