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
Employeeclass that holds a name, salary, and the date/time the employee joined (usedatetime.now()from thedatetimemodule). - 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 withstrftime("%c")). - Write client code that creates a couple of
Employeeobjects 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 toEmployeethat 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
Employeeholding a statutory minimum salary, set to12000. - 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
Employeeis 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