Chapter 16 Object Orientation

16.1 What is Object Orientation?

  • An effective program often utilizes an object-oriented approach in its design. Therefore, most coding languages evolve as an object-oriented language.
  • In OO design, systems consist of collaborating sets of well-defined objects. One object sends a message to another object to achieve some goal.
  • The message is implemented as a function call, often referred to as a method. Therefore, methods are associated with specific objects and depend on the nature of the object as well.
## Examples of String object's methods
newstring = text.upper()
newstring = text.lower()
newstring = text.split()
  • An object’s blueprint determines its structure. This blueprint is referred to as the class definition. When we create a new object from the object’s class definition, the object is an instance of the class and the process is called instantiation.
  • In addition to methods, an object can also have attributes or properties that we can get and set. We can also determine whether to hide these attributes and make them available only through the methods (private attributes) or expose them to the outside world (public attributes).

16.2 Class Defition

  • A Class definition is a template for a new object instance. While there are many pre-defined classes available in default Python modules, we can define our own class depending on the tasks at hand.
  • In a class definition, usually we include:
    • the mechanism and data required to create an new instance of the class (constructor method(s)),
    • its attributes (both private and public attributes),
    • the methods that can be used to set and get attributes or change the state ob the object (class methods).
# Define a class
class Employee(object):
    "Employee Class"
    # Constructor method
    def __init__(self, first, last, pay):
        self.first = first
        self.last = last
        self.email = first + '.' + last + '@email.com'
        self.pay = pay
    # instance method
    def fullname(self):
        return '{} {}'.format(self.first, self.last)
emp_1 = Employee('Alvin', 'Chen', 50000)
emp_2 = Employee('John', 'Doe', 60000)

emp_1.fullname()
'Alvin Chen'
emp_2.fullname()
'John Doe'

16.3 Types of Methods

  • Class Method: a method bound to the class and with cls as the first default argument
  • Static Method: a self-contained method bound to the class
  • Instance Method: a method bound to the object instance of the class with self as the first default argument
class Employee(object):

    num_of_emps = 0
    raise_amt = 1.04

    def __init__(self, first, last, pay):
        self.first = first
        self.last = last
        self.email = first + '.' + last + '@email.com'
        self.pay = pay
        Employee.num_of_emps += 1

    def fullname(self):
        return '{} {}'.format(self.first, self.last)

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amt)

    @classmethod
    def set_raise_amt(cls, amount):
        cls.raise_amt = amount

    @classmethod
    def from_string(cls, emp_str):
        first, last, pay = emp_str.split('-')
        return cls(first, last, pay)

    @staticmethod
    def is_workday(day):
        if day.weekday() == 5 or day.weekday() == 6:
            return False
        return True
emp_1 = Employee('Corey', 'Schafer', 50000)
emp_2 = Employee('Test', 'Employee', 60000)

Employee.set_raise_amt(1.05)

print(Employee.raise_amt)
1.05
print(emp_1.raise_amt)
1.05
print(emp_2.raise_amt)
1.05
emp_str_1 = 'John-Doe-70000'
emp_str_2 = 'Steve-Smith-30000'
emp_str_3 = 'Jane-Doe-90000'

first, last, pay = emp_str_1.split('-')

#new_emp_1 = Employee(first, last, pay)
new_emp_1 = Employee.from_string(emp_str_1)

print(new_emp_1.email)
John.Doe@email.com
print(new_emp_1.pay)
70000
import datetime
my_date = datetime.date(2016, 7, 11)

print(Employee.is_workday(my_date))
True

16.4 Class Inheritance

  • With the class definition, we can create as many instances of the class as needed.
  • Moreover, with the class definition, we can create suborindate or superordinate classess based on the original class. These sub-classess have taxonomic relationships with the original super class.
  • Extend the original class constructor methods using super()
class Employee:

    raise_amt = 1.04

    def __init__(self, first, last, pay):
        self.first = first
        self.last = last
        self.email = first + '.' + last + '@email.com'
        self.pay = pay

    def fullname(self):
        return '{} {}'.format(self.first, self.last)

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amt)


class Developer(Employee):
    raise_amt = 1.10

    def __init__(self, first, last, pay, prog_lang):
        super().__init__(first, last, pay)
        self.prog_lang = prog_lang


class Manager(Employee):

    def __init__(self, first, last, pay, employees=None):
        super().__init__(first, last, pay)
        if employees is None:
            self.employees = []
        else:
            self.employees = employees

    def add_emp(self, emp):
        if emp not in self.employees:
            self.employees.append(emp)

    def remove_emp(self, emp):
        if emp in self.employees:
            self.employees.remove(emp)

    def print_emps(self):
        for emp in self.employees:
            print('-->', emp.fullname())


dev_1 = Developer('Alvin', 'Chen', 50000, 'Python')
dev_2 = Developer('John', 'Doe', 60000, 'R')

mgr_1 = Manager('Anne', 'Mary', 90000, [dev_1])

print(mgr_1.email)
Anne.Mary@email.com
mgr_1.add_emp(dev_2)
mgr_1.remove_emp(dev_2)

mgr_1.print_emps()
--> Alvin Chen

16.5 Special Methods

  • Dunder methods (double-underscore)
  • To avoid overloading the expressions in coding
class Employee:

    raise_amt = 1.04

    def __init__(self, first, last, pay):
        self.first = first
        self.last = last
        self.email = first + '.' + last + '@email.com'
        self.pay = pay

    def fullname(self):
        return '{} {}'.format(self.first, self.last)

    def apply_raise(self):
        self.pay = int(self.pay * self.raise_amt)

    def __repr__(self):
        return "Employee('{}', '{}', {})".format(self.first, self.last, self.pay)

    def __str__(self):
        return '{} - {}'.format(self.fullname(), self.email)

    def __add__(self, other):
        return self.pay + other.pay

    def __len__(self):
        return len(self.fullname())


emp_1 = Employee('Corey', 'Schafer', 50000)
emp_2 = Employee('Test', 'Employee', 60000)

# print(emp_1 + emp_2)

print(len(emp_1))
13

16.6 Property Decorator

  • @property: make a method function like attribute-accessing
  • @NAME.setter: make a method function like class-attribute assigning
  • @NAME.deleter: make a method function like class-attribute deleting
class Employee:

    def __init__(self, first, last):
        self.first = first
        self.last = last

    @property
    def email(self):
        return '{}.{}@email.com'.format(self.first, self.last)

    @property
    def fullname(self):
        return '{} {}'.format(self.first, self.last)
    
    @fullname.setter
    def fullname(self, name):
        first, last = name.split(' ')
        self.first = first
        self.last = last
    
    @fullname.deleter
    def fullname(self):
        print('Delete Name!')
        self.first = None
        self.last = None


emp_1 = Employee('John', 'Smith')
emp_1.fullname = "Corey Schafer"

print(emp_1.first)
Corey
print(emp_1.email)
Corey.Schafer@email.com
print(emp_1.fullname)
Corey Schafer
del emp_1.fullname
Delete Name!

16.7 Checking Functions

  • isinstance(): Check an instance’s type
  • issubclass(): Check class inheritance

16.8 Name Mangling

  • __NAME: Any identifier of this form within the class is textually replaced with _classname__NAME, where classname is the current class name, with leading underscore(s) stripped.
  • This is for the purpose of creating private variables to the class.

16.9 Reference