# To create a class, the "class" keyword is used along with a class name that starts with an uppercase letter. # SYNTAX: class ClassName(): class SampleClass(): # The properties that all SampleClass objects must have are defined in a method called __init__. # Any number of parameters can be passed to __init__(), but the first parameter should always be self. # When a SampleClass instance is created, that instance is automatically passed to the self parameter. def __init__(self, year): self.year = year # Methods are functions defined inside a class. def show_year(self): print(f'The year is: {self.year}') # Creating an instance of SampleClass by calling the class and passing the required argument myObj = SampleClass(2020) # To access properties and call methods of an object instance, use dot notation print(myObj.year) # Result: 2020 myObj.show_year() # Result: The year is: 2020 # [SECTION] Fundamentals of OOP # There are four main fundamental principles in OOP # Encapsulation # Inheritance # Polymorphism # Abstraction # [SECTION] Encapsulation # Encapsulation is a mechanism of wrapping the attributes and the methods that act on them together as a single unit. # In encapsulation, the attributes of a class are hidden from other classes and can be accessed only through the methods of the current class. # Therefore, it is also known as data hiding. # To achieve encapsulation: # Declare the attributes of a class. # Provide getter and setter methods to view and modify the attribute values. # Why use encapsulation? # The fields of a class can be made read-only or write-only. # A class can have full control over what is stored in its fields. class Person(): def __init__(self): # The underscore prefix is a convention that signals: "Be careful with this attribute, it's intended for internal use within the class." # Protected attribute: _name self._name = "John Doe" # Mini Exercise solution self._age = 20 # setter method def set_name(self, name): self._name = name # getter method def get_name(self): print(f'Name of Person: {self._name}') # Mini Exercise solution # setter method set_age def set_age(self, age): self._age = age # getter method get_age def get_age(self): print(f'Age of Person: {self._age}') # Creating an instance of the Person class person1 = Person() print("Encapsulation:") # getter person1.get_name() # Result: Name of Person: John Doe # setter person1.set_name("Bob Doe") person1.get_name() # Result: Name of Person: Bob Doe # Mini Exercise 1: # Add another protected attribute called "age" # Create the necessary getter and setter methods # Output: "Age of Person: " # Mini Exercise solution person1.set_age(20) person1.get_age() # Result: Age of Person: 20 # [SECTION] Inheritance # Inheritance is the transfer of characteristics from one class (the parent) to other classes (the children) derived from it. # For example, an employee is a person with additional attributes and methods. # To create a subclass (inherited class), specify the parent class in the class definition. # SYNTAX: class ChildClassName(ParentClassName): class Employee(Person): def __init__(self, employeeId): super().__init__() self._employeeId = employeeId # getter method def get_employeeId(self): print(f"The Employee ID is {self._employeeId}") # setter method def set_employeeId(self, employeeId): self._employeeId = employeeId # custom methods # Custom methods are methods you create in a class to do specific tasks that aren't built-in or inherited. def get_details(self): print(f"{self._employeeId} belongs to {self._name}") # Creating an instance of the Employee class emp1 = Employee("Emp-001") emp1.get_details() # Result: Emp-001 belongs to John Doe print("Inheritance:") emp1.set_name("Bob Doe") emp1.get_details() # Result: Emp-001 belongs to Bob Doe # Mini Exercise 2: # Create a new class called Student that inherits Person with the additional attributes and methods. # Attributes: # Student No # Course # Year Level # Methods: # Necessary getters and setters # get_detail: prints the output ` is currently in year taking up ` # Solution: class Student(Person): def __init__(self, studentNo, course, year_level): super().__init__() self._studentNo = studentNo self._course = course self._year_level = year_level # getters def get_studentNo(self): print(f"Student number of Student is {self._studentNo}") def get_course(self): print(f"Course of Student is {self._course}") def get_year_level(self): print(f"The Year Level of Student is {self._year_level}") # setters def set_studentNo(self, studentNo): self._studentNo = studentNo def set_course(self, course): self._course = course def set_year_level(self, year_level): self._year_level = year_level # custom method def get_details(self): print(f"{self._name} is currently in year {self._year_level} taking up {self._course}.") # Creating an instance of the Student class student1 = Student("stdt-001", "Computer Science", 1) student1.set_name("Brandon Smith") # Result: Brandon Smith student1.get_details() # Result: Brandon Smith is currently in year 1 taking up Computer Science. # [SECTION] Polymorphism # A child class inherits all the methods from its parent class. However, in some situations, a method inherited from the parent class may not fully suit the child class. In such cases, you need to re-implement the method in the child class. # Polymorphism in Python refers to defining methods in the child class with the same name as those in the parent class. # Through inheritance, the child class can reuse methods from the parent class, but it can also modify or override them to fit its own needs. # Polymorphism with Inheritance class Zuitt(): def tracks(self): print('We are currently offering 3 tracks(developer career, pi-shape career, and short courses)') def num_of_hours(self): print('Learn web development in 360 hours!') class DeveloperCareer(Zuitt): # Override the parent's num_of_hours() method def num_of_hours(self): print('Learn the basics of web development in 240 hours!') class PiShapedCareer(Zuitt): # Override the parent's num_of_hours() method def num_of_hours(self): print('Learn skills for no-code app development in 140 hours!') course1 = DeveloperCareer() course2 = PiShapedCareer() print("Polymorphism:") course1.num_of_hours() # Result: Learn the basics of web development in 240 hours! course2.num_of_hours() # Result: Learn skills for no-code app development in 140 hours! # Mini Exercise 3: # Add another child class named ShortCourses # Method “num_of_hours” should print: Learn advanced topics in web development in 20 hours! # Solution: class ShortCourses(Zuitt): def num_of_hours(self): print("Learn advanced topics in web development in 20 hours!") # Mini Exercise solution course3 = ShortCourses() # Mini Exercise solution course3.num_of_hours() # Result: Learn advanced topics in web development in 20 hours! # Polymorphism with Functions and Objects # You can use different functions, class methods, or objects to demonstrate polymorphism. # A function can be created that accepts any object, allowing polymorphic behavior. class Admin(): def is_admin(self): print(True) def user_type(self): print('Admin User') class Customer(): def is_admin(self): print(False) def user_type(self): print('Regular User') # Define a test function that takes an object called obj def test_function(obj): obj.is_admin() obj.user_type() # Create object instances for Admin and Customer user_admin = Admin() user_customer = Customer() # Pass the created instances to the test_function test_function(user_admin) # Result: # True # Admin User test_function(user_customer) # Result: # False # Regular User # The test_function calls the methods of the object passed to it, producing different outputs depending on the object type. # Polymorphism with Class Methods # Python can use different class types in the same way. # For example, a for loop can iterate through a collection of objects. # The loop calls the same methods on each object, regardless of its class type. class TeamLead(): def occupation(self): print('Team Lead') def hasAuth(self): print(True) class TeamMember(): def occupation(self): print('Team Member') def hasAuth(self): print(False) tl1 = TeamLead() tm1 = TeamMember() for person in (tl1, tm1): # Call the occupation method on each object person.occupation() person.hasAuth() # Result: # Team Lead # True # Team Member # False # In each iteration, the variable "person" refers to either tl1 or tm1, depending on the order in the loop. This demonstrates polymorphism in action. # [SECTION] Abstraction # An abstract class can be considered a blueprint for other classes. It allows you to define a set of methods that must be implemented in any child class that inherits from it. # A class that contains one or more abstract methods is called an abstract class. # An abstract method is a method that is declared but contains no implementation. # Abstract classes are used to provide a common interface for different classes with different implementations. # By default, Python does not support abstract classes directly. However, it provides the abc module, which allows you to define Abstract Base Classes (ABCs). from abc import ABC, abstractmethod # Importing ABC and abstractmethod from the abc module # Polygon inherits from ABC, making it an abstract class class Polygon(ABC): # Abstract method that must be implemented by any subclass @abstractmethod # This method is meant to be overridden in subclasses; no implementation here def printNumberOfSides(self): # The pass keyword indicates that the method is intentionally left blank pass class Triangle(Polygon): def __init__(self): super().__init__() # Implementation of the abstract method def printNumberOfSides(self): print("This polygon has 3 sides.") class Pentagon(Polygon): def __init__(self): super().__init__() # Implementation of the abstract method def printNumberOfSides(self): print("This polygon has 5 sides.") # Create instances and call the method shape1 = Triangle() shape2 = Pentagon() print("Abstraction:") shape1.printNumberOfSides() # Result: This polygon has 3 sides. shape2.printNumberOfSides() # Result: This polygon has 5 sides.