Skip to main content

Object-Oriented Programming

Introduction​

This chapter introduces Python's approach to object-oriented programming through a running example: an Employee class. Python classes look a little different from Java or C#: there's no explicit private/public keyword — instead, Python uses a naming convention. A leading double underscore (e.g. __salary) triggers name mangling, which makes an attribute much harder to access from outside the class, giving you a practical form of encapsulation.

You'll define instance methods (which operate on a specific object) and class-wide members (variables and methods shared by every instance of the class), and you'll see how __init__ acts as the constructor.


Concepts​

Defining a class​

from datetime import datetime

class Employee:
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
self.__date_joined = datetime.now()

def pay_raise(self, amount):
self.__salary += amount

def to_string(self):
return (f"{self.__name}, salary: {self.__salary}, "
f"joined: {self.__date_joined.strftime('%c')}")

Encapsulation via name mangling​

emp = Employee("Alice", 30000)
# emp.__salary # AttributeError - name mangled, not directly accessible
print(emp.pay_raise(1000))

Default arguments for flexible methods​

def pay_bonus(self, percent=1, min_salary=0, max_salary=float("inf")):
if min_salary <= self.__salary <= max_salary:
self.__salary += self.__salary * (percent / 100)

Class-wide variables and methods​

class Employee:
minimum_salary = 12000 # class-wide variable, shared by all instances

@classmethod
def get_minimum_salary(cls):
return cls.minimum_salary

@classmethod
def set_minimum_salary(cls, value):
cls.minimum_salary = value

Examples​

from datetime import datetime

class Employee:
minimum_salary = 12000

def __init__(self, name, salary):
self.__name = name
self.__salary = max(salary, Employee.minimum_salary)
self.__date_joined = datetime.now()

def pay_raise(self, amount):
self.__salary += amount

def pay_bonus(self, percent=1, min_salary=0, max_salary=float("inf")):
if min_salary <= self.__salary <= max_salary:
self.__salary += self.__salary * (percent / 100)

def to_string(self):
return (f"{self.__name}, salary: {self.__salary:.2f}, "
f"joined: {self.__date_joined.strftime('%c')}")

@classmethod
def get_minimum_salary(cls):
return cls.minimum_salary

@classmethod
def set_minimum_salary(cls, value):
cls.minimum_salary = value


emp1 = Employee("Alice", 30000)
emp1.pay_raise(2000)
emp1.pay_bonus(10)
print(emp1.to_string())

print(Employee.get_minimum_salary())

Challenges​

1. Defining the Employee class​

  • Define an Employee class that holds a name, salary, and the date/time the employee joined (use datetime.now() from the datetime module).
  • Keep the instance variables private (name-mangled) to honour encapsulation.
  • Add a pay_raise(amount) method that adds the given amount to the employee's salary.
  • Add a to_string() method that returns a readable summary of the employee (format the joined date with strftime("%c")).
  • Write client code that creates a couple of Employee objects and calls these methods.
💡 Show Answer
from datetime import datetime

class Employee:
def __init__(self, name, salary):
self.__name = name
self.__salary = salary
self.__date_joined = datetime.now()

def pay_raise(self, amount):
self.__salary += amount

def to_string(self):
return (f"{self.__name}, salary: {self.__salary}, "
f"joined: {self.__date_joined.strftime('%c')}")


emp1 = Employee("Alice", 28000)
emp1.pay_raise(2000)
print(emp1.to_string())

2. Flexible pay_bonus()​

  • Add a pay_bonus() method to Employee that can be called three ways:
    • With no arguments — applies a fixed 1% bonus.
    • With a percentage argument — applies that percentage as a bonus.
    • With a percentage, a minimum salary, and a maximum salary — the bonus only applies if the employee's salary falls within that range.
💡 Show Answer
def pay_bonus(self, percent=1, min_salary=0, max_salary=float("inf")):
if min_salary <= self.__salary <= max_salary:
self.__salary += self.__salary * (percent / 100)

# Usage:
emp1.pay_bonus() # default 1% bonus
emp1.pay_bonus(10) # 10% bonus
emp1.pay_bonus(10, 20000, 50000) # 10% bonus, only if salary is in range

3. Class-wide statutory minimum salary​

  • Add a class-wide variable to Employee holding a statutory minimum salary, set to 12000.
  • Use this class variable in the constructor to ensure every employee earns at least this much.
  • Add class methods to get and set the statutory minimum salary, and call them from your test code.
💡 Show Answer
class Employee:
minimum_salary = 12000

def __init__(self, name, salary):
self.__name = name
self.__salary = max(salary, Employee.minimum_salary)
self.__date_joined = datetime.now()

@classmethod
def get_minimum_salary(cls):
return cls.minimum_salary

@classmethod
def set_minimum_salary(cls, value):
cls.minimum_salary = value


Employee.set_minimum_salary(15000)
print(Employee.get_minimum_salary())

emp = Employee("Bob", 10000) # will be bumped up to 15000

4. Auto-incrementing employee IDs​

  • Add an instance variable to hold each employee's ID number.
  • Add a class-wide variable holding the next available ID, incremented every time a new Employee is created.
💡 Show Answer
class Employee:
next_id = 1

def __init__(self, name, salary):
self.__name = name
self.__salary = salary
self.__date_joined = datetime.now()
self.__id = Employee.next_id
Employee.next_id += 1

def get_id(self):
return self.__id


emp1 = Employee("Alice", 28000)
emp2 = Employee("Bob", 32000)
print(emp1.get_id()) # 1
print(emp2.get_id()) # 2