Python – Object Oriented Programming – Exceptions

Integration with Python

Operator overloading : comparison

Object equality

class Customer:
  def __init__(self, name, balance):
    self.name, self.balance = name, balance
cust1 = Customer("John Fisher", 2100)
cust2 = Customer("John Fisher", 2100)

cust1 == cust2
> False

 

Variables are references

class Customer: 
  def __init__(self, name, balance, id): 
    self.name, self.balance = name, balance 
    self.id = id

cust1 = Customer("John Fisher", 2100, 123) 
cust2 = Customer("John Fisher", 2100, 123) 
cust1 == cust2 
> False

print(cust1)
<__main__.Customer at 0x16549e8e78>
print(cust2)
<__main__.Customer at 0x16549e7e51>
  • Due to the fact how the objects and variables representing them are stored
    • printing the value of the (customer) object –> prints the memory allocation chunk
    • the value = reference to the memory chunk ))> so when comparing we are comparing the memory chunks

Custom comparison

import numpy as np
# two diff arrays containing the same data
arr1 = np.array([1,2,3])
arr2 = np.array([1,2,3])
arr1 == arr2
> True
  • here numpy arrays –> Python considers them equal (as other Python objects like DataFrames
  • How to enforce -> special method __eq__() 

Overloading __eq__()

class Customer:
  def __init__(self, id, name):
    self.id, self.name = id, name
  # wil be called when == is used
  def __eq__(self, other):
    print("__eq__() is called")
    # returns True is all attributes match
    return (self.id == other.id) and \
           (self.name == other.name)
  • __eq__() is called when 2 objects of a class are compared using ==
  • accepts 2 args, self and other – object to compare
  • returns a Boolean

 

Comparison of objects

# Two equal objects
cust1 = Customer(213, 'John')
cust2 = Customer(213, 'John')
cust1 == cust2 
> __eq__() is called
> True

# two unequal object
cust1 = Customer(213, 'John')
cust2 = Customer(123, 'John')
cust1 == cust2 
> __eq__() is called
> False
  • here the __eq__ method is called and the comparison returns ‘True’ in the 1st case, False in the second case

Other comparisons

Operator Method
== __eq__()
!= __ne__()
>= __ge__()
<= __le__()
> __gt__()
< __lt__()
  • __hash__() to use objects as dictionary keys and in sets

Exercises

class BankAccount:
  def __init__(self, number, balance=0):
    self.balance = balance
    self.number = number
  def withdraw(self, amount):
    self.balance -= amount 
  def __eq__(self, other):
    return self.number == other.number   

acct1 = BankAccount(123, 1000)
acct2 = BankAccount(123, 1000)
acct3 = BankAccount(456, 1000)
print(acct1 == acct2)
> True
print(acct1 == acct3)
> False

 

class Phone:
  def __init__(self, number):
    self.number = number
  def __eq__(self, other):
    return self.number == other.number

class BankAccount:
  def __init__(self, number, balance=0):
    self.number, self.balance = number, balance
  def withdraw(self, amount):
    self.balance -= amount 
  def __eq__(self, other):
        return ((self.number == other.number) & (type(self) == type(other)))

acct = BankAccount(873555333)
pn = Phone(873555333)
print(acct == pn)
> False

 

Operator overloading : string representation

Printing an object

class Customer:
  def __init__(self, name, balance):
    self.name, self.balance = name, balance
cust = Customer("John", 300)
print(cust)
> <__main__.Customer at 0x1f46e8e9984>

# -------
import numpy as np
arr = np.array([1,2,3])
print(arr)
> [1 2 3]
  • There are 2 classes that can be a representation
    • __str__() 
      • print(obj), str(obj)
      • informal, for end user
      • string representation
      • print(np.array([1,2,3])) –>[1 2 3]
    • __repr__()
      • repr(obj), printing in console
      • formal, for developer
      • reprocducible representation
      • fallback for print() when __str__() is not available
      • repr(np.array([1,2,3])) –> np.array([1,2,3])

 

Implementation: str

class Customer:
  def __init__(self, name, balance):
    self.name, self.balance = name, balance
  def __str__(self):
    cust_str = """
    Customer:
      name: {name}
      balance: {balance}
    """.format(name = self.name,  \
               balance = self.balance)
    return cust_str

cust = Customer("Kev", 125)
print(cust)
> Customer:
    name: Kev
    balance: 125
  • the triple ” –> indication for multiline print
  • {} to fill in the values

 

Implementation: repr

class Customer: 
def __init__(self, name, balance): 
self.name, self.balance = name, balance 

def __repr__(self): 

return "Customer('{name}',{balance})".format(name = self.name, 
                                             balance = self.balance) 
cust = Customer("Kev", 125) 
cust 
> Customer("Kev", 125)
  • surround the string arguments with quotation marks in the __repr__() output

String formatting review

my_num = 5
my_str = "Hello"

f = "my_num is {}, and my_str is \"{}\".".format(my_num, my_str)
print(f)
> my_num is 5, and my_str is "Hello".

Exercise

class Employee:
  def __init__(self, name, salary=30000):
    self.name, self.salary = name, salary
  def __str__(self):
    s = "Employee name: {name}\nEmployee salary: {salary}".format(name=self.name, salary=self.salary)      
    return s
  def __repr__(self):   
    return "Employee('{name}', {salary})".format(name=self.name, salary=self.salary)

emp1 = Employee("Amar Howard", 30000)
print(repr(emp1))
emp2 = Employee("Carolyn Ramirez", 35000)
print(repr(emp2))
> Employee('Amar Howard', 30000)
> Employee('Carolyn Ramirez', 35000)

 

 

Exceptions

Exception handling

    • a = 1 –> a / 0 ==> ZeroDivisionError
    • a = 1 –> a + “Hi” ==> TypeError
    • a = [1,2,3] –> a[5] ==> IndexError
    • a = 1 –> a + b ==> NameError
  • Prevent the program from terminating when a exception is raised
  • Use the try – except – finally :
try:
  # try running code
except ExceptionNameHere:
  # Run this code if ExceptionNameHere happens
except AnotherExceptionNameHere:
  # Run this code if AnotherExceptionNameHere happens
...
finally:    # optional
  # run this code no matter what

 

Raising exceptions

def make_list_of_ones(length):
  if length <= 0:
    raise ValueError('Invalid Length')  # stops program and raises error
  return [1] * length

make_list_of_ones(-1)
> ValueError: Invalid Length

Exceptions are classes

Custom exceptions

  • Inherits from Exception or one of its subclasses
  • Usually an empty class
class BalanceError(Exception): pass
class Customer:
  def __init__(self, name, balance):
    if balance < 0:
      raise BalanceError("Balance must be positive")
    else:
      self.name, self.balance = name, balance

cust = Customer('Larry', -10)
> BalanceError: Balance must be positive
cust
> NameError : name 'cust' is not defined
  • the exception interrupted the constructor –> object not created

Catching custom exceptions

try:
  cust = Customer('Larry', -10)
except BalanceError:
  cust = Customer('Larry', 0)

Excercises

def invert_at_index(x, ind):
    try:
        return 1/x[ind]
    except ZeroDivisionError:
        print("Cannot divide by zero!")
    except IndexError:
        print("Index out of range!")
 
a = [5,6,0,7]
# Works okay
print(invert_at_index(a, 1))
> 0.1666666666
# Potential ZeroDivisionError
print(invert_at_index(a, 2))
> Cannot divide by zero!
# Potential IndexError
print(invert_at_index(a, 5))
> Index out of range!
class SalaryError(ValueError): pass
class BonusError(SalaryError): pass

class Employee:
  MIN_SALARY = 30000
  MAX_BONUS = 5000

  def __init__(self, name, salary = 30000):
    self.name = name    
    if salary < Employee.MIN_SALARY:
      raise SalaryError("Salary is too low!")      
    self.salary = salary
    
  # Rewrite using exceptions  
  def give_bonus(self, amount):
    if amount > Employee.MAX_BONUS:
       raise BonusError("The bonus amount is too high!")  
        
    elif self.salary + amount <  Employee.MIN_SALARY:
       raise SalaryError("The salary after bonus is too low!")
      
    else:  
      self.salary += amount

It’s better to list the except blocks in the increasing order of specificity, i.e. children before parents, otherwise the child exception will be called in the parent except block.