<jupyter_start><jupyter_text>4 Pillars of OOP<jupyter_code># Encapsulation
# a simple class to explain Encapsulation
class Human():
    _name = None
    _age = 0

    def __init__(self, name, age):
        self._name = name
        self._age = age

    def greet(self, name):
        print(f'Hello {name}. My name is {self._name}. Nice to meet you!')
# The meaning of Encapsulation here is the intenal data (_name, _age) and the behavior or method (greet()) are ecapsulated and organized together inside the Human class.
# They are tied together and have meaning together
human1 = Human('Ryan', 13)
human1.greet('Maia')
human1._name
# Abstraction
# means a class implement a functionality and hide the detail implmentation, but only expose the interface for users to use that functionality.
# Users never need to know the detail inside implemenation to be able to use the class
# For example, a laptop is extremmely complciated to build, but it expose the interface (screen, keyboard, mouse) for users to use it.
# Users never need to know how a laptop is built to be able to use it 
# simple class to explain Abstraction
class DeliveryRobot():
    _model = None

    def __init__(self, model):
        self._model = model

    def deliver(self, package, address):
        # locate package

        # pick package

        # locate the delivery address

        # fly from warehouse to the address

        # drop the package

        print(f'Package {package} is successfully delivered to address {address}')

# A user of the DeliveryRobot class do NOT need to know the detail inside of the robot
# just need to instantiate an Robot object and simply call "deliver()" method
robot1 = DeliveryRobot('Model 1')
robot1.deliver('TV', "New York")
# Inheritance
# means a class can inherit from other classes to reuse the code have been written.
# This also means to avoid code duplication
# Simple class to explain Inheritance
class Student(Human):
    pass
# We did not define any attrubutes or method for Student class
# But because Student is inheritted from Human, so it should already inherit the attributes and methods from Human
student1 = Student('Maia', 40)
student1.greet('Myra')
# Normally a class inherit the classes and extend the functionalities 
class Student(Human):
    _student_id = None
    _grade = None

    def __init__(self, name, age, student_id, grade):
        super().__init__(name, age)
        self._student_id = student_id
        self._grade = grade

    def study(self, course):
        print(f'I am studying {course}')
student2 = Student('Arby', 9, '12345', 4)
student2.greet('Maia')
student2.study('Python')
human2 = Human('ABC', 1)
human2.study('Math')
# Indirect inheritance or multi-level inheritance
# means when a class inherits from a class, it actually also inherits attributes and methods of all ancesters of that classs too
class CollegeStudent(Student):
    _college = None
    _major = None

    def __init__(self, name, age, student_id, grade, college, major):
        super().__init__(name, age, student_id, grade)
        self._college = college
        self._major = major

    def work_part_time(self):
        print('I am a college student. I can work part-time')

    def research(self, topic):
        print(f'I study in major {self._major}. I am researching about {topic}')
student4 = CollegeStudent('Ryan', 17, '123', 'Excellent', 'Harvard', 'Computer')
student4.greet('Mom')
student4.study('Conputer')
student4.research('Database')
# Multi Inheritance or Multiple Inheritance
# means a class can inherits multiple classes at the same time, this is different with multiple-level inheritance, a.k.a. indirect inheritance
class Driver(Human):
    _driver_id = None

    def __init__(self, name, age, driver_id):
        super().__init__(name, age)
        self._driver_id = driver_id

    def drive(self):
        print(f'I can drive. My driver license is {self._driver_id}. Zoom zoooom.')
# Now, we can create a simple class to explain Multi-inheritance
class Graduate(CollegeStudent, Driver):
    _certificate_id = None

    def __init__(self, name, certicate_id, driver_id):
        self._name = name
        self._certificate_id = certicate_id
        self._driver_id = driver_id

    def seek_job(self):
        print(f'I am a Graduate. My Certificate is {self._certificate_id}. I can drive too.')
        print('I am seeking for a good job.')
graduate1 = Graduate('Maia', 'Certificate - 111', 'Driver ID - 123')
graduate1.drive()
graduate1.research('Math')
graduate1.seek_job()<jupyter_output>I am a Graduate. My Certificate is Certificate - 111. I can drive too.
I am seeking for a good job.
{'fraction_non_alphanumeric': 0.08650742414460942, 'fraction_numerical': 0.010544437271357865, 'mean_word_length': 3.6573146292585172, 'pattern_counts': {'":': 0, '<': 4, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '16733', 'license': 'Apache-2.0', 'n_tokens_mistral': 1387, 'n_tokens_neox': 1296, 'n_words': 551, 'path': 'class/four_pillars.ipynb', 'repo_name': 'MaiaNgo/python-advanced', 'size': '4647'}