Python – Object Oriented Programming – Classes and Objects

Object Oriented Programming – Intro

What’s OOP ?

Procedural vs Object Oriented

  • Procedural programming :
    • sequential coding
    • OK for Data Analysis
  • Object Oriented
    • Coding as interaction for object
    • Frameworks and tools
    • usability

Terminology

  1. Object = state + behaviour  ==> a FIlou a dog has 4 legs and a tail, and can bark and fetch a ball
  2. Encapsulation = handling data with code operating on it
  3. Classes are like blueprints for possible states & behaviour
    1. class = abstract pattern  (example: dog)
    2. object = particular representation of a class  ( = Filou )

Object in Python

Everyhting is an object in Python  :

    • 5 –> class : int
    • “Hello” –> class : str
    • np.mean() –> class : function
# How to find out what kind of class
import numpy as np
a = np.array([1,2,8,5])
print(type(a))

> <class 'numpy.ndarray'>
  • in case of Filou –> type(Filou) would be class Dog

 

Attributes and Methods

State —  Attributes

import numpy as np
a = np.array([1,2,8,5])
# shape attribute
print(a.shape)
> (4,)
  • attribute — variable(s) — obj.my_attribute
  • in case of Filou –> Filou’s attributes = ‘4 legs’, ‘1 tail’

Behavior — Methods

import numpy as np
a = np.array([1,2,8,5])
# reshape method 
a = a.reshape(2,2)
print(a)

> [[1 2]
   [8 5]]
  • method — function() — obj.my_method()
  • In case of Filou –> methods are ‘barking’, ‘fetching balls’

Listing all attributes and methods

import numpy as np
a = np.array([1,2,8,5])
print(dir(a))

> ['T', '__abs__', '__add__', ... ... 'transpose', 'var', 'view']
  • listing of Filou –> dir(Filou) –> [‘legs’,’tail’,’bark’,’fetch_balls’ ]
  • help() to see documentation on object –> provided the class documentation filled in

 

Class anatomy : basics

 

Basic Class

class Customer:
  # Code for class
  pass
  • class <>: starts a class definition
  • code inside class is indented
  • use pass to create an empty class
c1 = Customer()
c2 = Customer()
  • Use Classname() to create an object of class ClassName

 

Add methods to a class

class Customer:
  # creating a method ( function within the class )
  def identify(self, name):
    print("I am customer " + name)
  • method def (inition) = function within a class
  • use self as the 1st argument in method definition
cust = Customer()
cust.identify("Marie")

> I am customer Marie
  • ignore self when calling method on an object –> in this case name is the parameter for identify()

What is self ?

  • classes are templates, how to refer to data of a particular object ?
  • self is a stand-in for a particular object used in class definition
  • self should be the first argument of any method .
  • Python will take care of self when the method is called from an object
    • cust.identify("Marie") is being interpreted as Customer.identify(cust, "Marie")

Attributes in a class

  • encapsulation –> bundling data with methods that operate on the data.
    • Customer name should be an attribute instead of an parameter through a method
  • attributes are created by assignment in methods .

Add an attribute to a class

class Customer:
    # set the name attribute to new_name
    def set_name(self, new_name):
        # create attribute by assigning a name
        self.name = new_name     # <-- .name attrib. is created here

cu5 = Customer()        # <--   .name does not exist yet
cu.set_name("John")     # <--   .name is created and set to "John" 
print(cu.name)          # <--   as the name-attribute is created, it can be used
> John

New version with identify

class Customer:
  def set_name(self, new_name):
    self.name = new_name
  # Using the name-attribute from the object itself 
  def identify(self):
    print("It is I, inspector " + self.name)

cust01 = Customer()
cust01.set_name("Vlad")
cust01.identify()
> It is I, inspector Vlad

Exercise

class MyCounter:
    def set_count(self, n):
        self.count = n
    def triple_num(self):
        self.count = self.count * 3

number1 = MyCounter()
number1.set_count(3)
number1.triple_num()
number1.count
> 15
class Employee:
  def set_name(self, new_name):
    self.name = new_name
  # Add set_salary() method
  def set_salary(self, new_salary):
      self.salary = new_salary

# Create an object emp of class Employee  
emp = Employee()
# Use set_name to set the name of emp to 'Korel Rossi'
emp.set_name('Korel Rossi')
# Set the salary of emp to 50000
emp.set_salary(50000)
class Employee:
    def set_name(self, new_name):
        self.name = new_name

    def set_salary(self, new_salary):
        self.salary = new_salary 

    def give_raise(self, amount):
        self.salary = self.salary + amount

    # Add monthly_salary method that returns 1/12th of salary attribute
    def monthly_salary(self):
        return self.salary / 12

    
emp = Employee()
emp.set_name('Korel Rossi')
emp.set_salary(50000)

# Get monthly salary of emp and assign to mon_sal
mon_sal = emp.monthly_salary()

# Print mon_sal
print(mon_sal)

Class anatomy  : The __init__ constructor

Methods and attributes

class MyClass:=
  # function definition in class
  # First Argument = self
  def my_method1(self, other args...):
    # do things
  
  def my_method2(self, my_attribute):
    # attribute by assignment
    self.my_attribute = my_attribute
    ...
  • Methods are function definitions within a class
  • self as the 1st argument
  • Attributes defined by assignment
  • Referral to attributes in class via self.___

Constructor __init__()

class Customer:
  def __init__(self, name):
    self.name = name   #  <-- Create the name attribute and set it to name parameter
    print("The __init__ method was called")

cust02 = Customer("Alfredo")      # <-- __init__ is implicitly called
> The __init__ method was called
print(cust02.name)
> Alfredo
  • Add data to object when creating it ?
  • Constructor __init__() method is called every time an object is created
class Customer: 
  def __init__(self, name, balance): 
    self.name = name 
    self.balance = balance # <-- Create the name attribute balance
    print("The __init__ method was called")

cust04 = Customer("Alfredo", 954.42)
> The __init__ method was called
print(cust04.name)
> Alfredo
print(cust04.balance)
> 954.42
  • with default parameters
class Customer: 
  def __init__(self, name, balance=500): 
    self.name = name 
    self.balance = balance 
    print("The __init__ method was called") 

cust05 = Customer("Allie") # no balance is given
print(cust04.name) 
> Allie
print(cust05.balance) 
> 500

Attributes in Methods

class MyClass:
  def my_method1(self, attr1):
    self.attr1 = attr1
    ...
  def my_method2(self, attr2):
    self.attr2 = attr2
    ...
obj = MyClass()
obj.my_method1(val1)  # attr1 created
obj.my_method2(val2)  # attr2 created

Attributes in constructor

class MyClass:
  def __init__(self, attr1, attr2):
    self.attr1 = attr1
    self.attr2 = attr2
    ...
obj = MyClass(val1, val2)   # all attribs are created)
  • easier to know all attributes
  • attributes are created when the object is created
  • more usable and maintainable code 

Best Practices

  1. Initialize attributes in __init__()
  2. Naming
    1. CamelCase for class
    2. lower_snake_case for functions and attributes
  3. Keep self as self
    1. readable –> self can be renamed but less readable
  4. Use docstrings
class Custo:
    """ Test on Docstrings """
    def __init__(self, name="S", balance=200):
        self.name = name
        self.balance = balance
        print("The __init__ method was called")

 

Exercise on self / __init __

# Import datetime from datetime
from datetime import datetime

class Employee:
    
    def __init__(self, name, salary=0):
        self.name = name
        if salary > 0:
          self.salary = salary
        else:
          self.salary = 0
          print("Invalid salary!")
          
        # Add the hire_date attribute and set it to today's date
        self.hire_date = datetime.today()
        
   # ...Other methods omitted for brevity ...
      
emp = Employee("Korel Rossi", -1000)
print(emp.name)
print(emp.salary)

> Invalid salary!
> Korel Rossi
> 0

Exercise

import numpy as np
class Point:
  def __init__(self, x=0.0, y=0.0):
    self.x = x
    self.y = y
  def distance(self):
    return np.sqrt(self.x ** 2 + self.y ** 2 )
  def reflect(self, axis):
    if axis == 'x':
      self.y = - self.y
    elif axis == 'y':
      self.x = - self.x
    else
      print("Invalid axis")