Python Forum
[Classes] Class Intermediate: Inheritance
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[Classes] Class Intermediate: Inheritance
#1
Be sure you understand the concepts in Class Basics, before reading this tutorial. I will also be using a few ideas from Class Intermediate: Operator Overloading, so it can't hurt to be familiar with those as well.

We've seen how we can use classes to create our own specialized objects, with whatever attributes and methods we want. But often you don't just want to create one type of object, you want to create a group of objects that share some similarities. Think of the various containers in Python: lists, dicts, tuples, sets. They all share certain things (they can all be used with len()), but they have differences as well.

For this tutorial, we'll consider a situation where you want to create a program for handling empoyees at a particular business. They have three kinds of employees:

* Managers, who have an office, a phone number, and a salary.
* Technical Staff, who have a cubicle, a phone number, and an hourly wage.
* Field Investigators, who have a phone number and an hourly wage.

Now, we could do it all in one class:

class AnyEmployee(object):
   
   def __init__(self, grade, title, first, last, location, phone, pay):
      self.grade = grade
      self.title = title
      self.name = (last, first)
      self.location = location
      self.phone = phone
      self.pay = pay
      
   def contact_info(self):
      text = ', '.join(self.name) + ' ('
      if self.location:
         text += self.location + ', '
      text += self.phone + ')'
      return text
   
   def weekly_pay(self, hours = 0):
      if self.grade == 'Manager':
         return round(self.pay / 52, 2)
      else:
         week = self.pay * hours
         if hours > 40:
            week += self.pay * 0.5 * (hours - 40)
         return week
You might wonder what's wrong with that. It only takes a couple if statements to handle the different cases. Indeed, we could define a few employees:

bob = AnyEmployee('Manager', 'Director of Analysis', "Bob", 'Dobbs', '801', 'x0888', 130130)
craig = AnyEmployee('Staff', 'Statistician', 'Craig', "O'Brien", '802-18', 'x7666', 25)
ronsarde = AnyEmployee('Investigator', 'Inspector', 'Sebastian', 'Ronsarde', None, '123-4567', 18)
and get the output we're looking for:

>>> bob.contact_info()
'Dobbs, Bob (801, x0888)'
>>> ronsarde.contact_info()
'Ronsarde, Sebastian (123-4567)'
>>> bob.weekly_pay()
2502.5
>>> craig.weekly_pay(45)
1187.5
Indeed, in this case it really isn't a problem. But this is just a simple case made up for a tutorial. In real life the situation gets more complicated. You may have a dozen different characteristics of your classes that need to interact in a dozen different ways. Instead of a confusing mass of conditional statements and empty variables, it works better to break the parts out into different classes that combine through inheritance.

So what is inheritance, and how does it work? To start with, let's simplify our employee class to cover just what is covered by the typical employees:

class StandardEmployee(object):
   """
   The typical employee at the ACME company.
   
   The employee's name is stored in last, first format.
   
   Attributes:
   grade: The type of employee. (str)
   location: Where the employee's desk is. (str)
   name: The employee's name. (tuple of str)
   pay: The employee's salary or hourly wage. (float)
   phone: The employee's business phone number. (str)
   title: The employee's position. (str)
   
   Methods:
   contact_info: Details on how to get in touch with the employee. (str)
   weekly_pay: Determine the employee's pay for a given week. (float)
   
   Overridden Methods:
   __init__
   """
   
   def __init__(self, grade, title, first, last, location, phone, pay):
      """
      Set up the employee's basic attributes.
      
      Parameters:
      first: The employee's first name. (str)
      grade: The type of employee. (str)
      last: The employee's last name. (str)
      location: Where the employee's desk is. (str)
      pay: The employee's hourly wage. (float)
      phone: The employee's business phone number. (str)
      title: The employee's position. (str)
      """
      self.grade = grade
      self.title = title
      self.name = (last, first)
      self.location = location
      self.phone = phone
      self.pay = pay
      
   def contact_info(self):
      """
      Details on how to get in touch with the employee. (str)
      """
      text = '{}, {} ({}, {})'.format(self.name[0], self.name[1], self.location, self.phone)
      return text
   
   def weekly_pay(self, hours):
      """
      Determine the employee's pay for a given week. (float)
      
      Parameters:
      hours: How many hours the employee worked. (float)
      """
      week = self.pay * hours
      if hours > 40:
         week += self.pay * 0.5 * (hours - 40)
      return week
The StandardEmployee class has hourly pay and a location, both of which two out of three types of employees have. Of course, this means StandardEmployee is basically the same as a technical staff employee. Note that it is missing a few things: the grade of the employee, and the if clauses. That's fine, because we won't need them if we know we are creating a technical staff employee object. But what do we do about managers and field investigators? We inherit from the StandardEmployee class:

class Manager(StandardEmployee):
   """
   A management employee at the ACME Company.
   
   Attributes;
   pay: The employee's salary. (float)
   
   Overridden Methods:
   weekly_pay
   """
      
   def weekly_pay(self):
      """
      Determine the employee's pay for a given week. (float)
      """
      return round(self.pay / 52, 2)
   
class Investigator(StandardEmployee):
   """
   A field investigator for the ACME Company.
   
   Overridden Methods:
   contact_info
   """
      
   def contact_info(self):
      """
      Details on how to get in touch with the employee. (str)
      """
      text = '{}, {} ({})'.format(self.name[0], self.name[1], self.phone)
      return text
Note the class definitions:

class Manager(StandardEmployee):
...
class Investigator(StandardEmployee):
Every class we've done so far in these tutorials has had "(object)" after the class name, but now we have "(StandardEmployee)". The thing in parentheses after the class name is the base class. The base class is the class the new class inherits from. Base classes are also sometimes called parent classes or superclasses, usually depending on who you learned object oriented programming from.

But what does that mean? Note that the Manager and Investigator classes only define one method each, and neither of them has an __init__ method. They inherit the methods they don't define from their base class. You don't define __init__ and contact_info for Manager, it gets them from StandardEmployee. Likewise Investigator gets __init__ and weekly_pay from StandardEmployee.

But they both have "Overridden Methods" listed in their doc strings. If you call the contact_info method of an Investigator instance, it will use the one defined for that class. If you call the weekly_pay method of an Investigator instance, Python won't find one in the Investigator class, so it will look at the base class (StandardEmployee) and use the one it finds there. If you call the __repr__ method of an Investigator instance, Python won't find one in either the Investigator or StandardEmployee classes, so it will use the one it find's in the base class of StandardEmployee: the object class.

If you make some new instances of our new classes:

# example employees
bob2 = Manager('Director of Analysis', "Bob", 'Dobbs', '801', 'x0888', 130130)
craig2 = StandardEmployee('Statistician', 'Craig', "O'Brien", '802-18', 'x7666', 25)
ronsarde2 = Investigator('Inspector', 'Sebastian', 'Ronsarde', None, '123-4567', 18)
You can see this in action:

>>> ronsarde2.contact_info()
'Ronsarde, Sebastian (123-4567)'
>>> ronsarde2.weekly_pay(40)
720
>>> repr(ronsarde2)
'<__main__.Investigator object at 0x02446990>'
It may seem kind of screwey that we have classes for managers and field investigators, but that the technical staff uses the StandardEmployee class. You could just rename it Staff, or:

class Staff(StandardEmployee):
   pass
would make another class that would just inherit everything from StanardEmployee. There is a lot of theory about how powerful object oriented programming is, but in many ways it is just a good way to organize your code so that it makes sense. It keeps the code for the employees all in one place. When you need to change it for a specific kind of employee, you do so. So your solution to issues like these are going to depend on you and your program, and what helps your program make sense. We're also going to get to some more complicated solutions, but I want to explore what we have a little first.
Craig "Ichabod" O'Brien - xenomind.com
I wish you happiness.
Recommended Tutorials: BBCode, functions, classes, text adventures


Messages In This Thread
Class Intermediate: Inheritance - by ichabod801 - Sep-15-2016, 11:16 PM
RE: Class Intermediate: Inheritance - by ichabod801 - Sep-15-2016, 11:18 PM
RE: Class Intermediate: Inheritance - by ichabod801 - Sep-15-2016, 11:25 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  [Classes] Class Intermediate: Operator Overloading ichabod801 3 10,311 Sep-15-2016, 11:07 PM
Last Post: ichabod801

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020