09. Introduction to Object-Oriented Programming II#

Last time#

  • What is object-oriented programming?

  • Class

  • Attributes

  • Iterable object and iterator

Today#

Review class#

  • Create and use new object with class

  • Create a class object involves…

    • Define the name

    • Define the attributes that belongs to this object

      1. Instance attributes

      2. Method attributes

      3. Class attributes

  • Invoke the attributes via dot notation

Inheritance#

  • Everytime you create a class object like this, it inherits the attributes of Python object.




  • Why do we need this?

class Human:
    def __init__(self, first_name, familiy_name):
        self.first_name = first_name
        self.family_name = family_name
    
class Teacher:
    def __init__(self, first_name, familiy_name, teacher_id):
        self.first_name = first_name
        self.family_name = family_name
        self.teacher_id = teacher_id

class Student:
    def __init__(self, first_name, familiy_name, student_id):
        self.first_name = first_name
        self.family_name = family_name
        self.student_id = student_id
  • A lot of lines is repeated in these classes. They are redundant and not easy to maintain.


class Human:
    def __init__(self, first_name, family_name):
        self.first_name = first_name
        self.family_name = family_name
    
class Teacher(Human):
    def __init__(self, first_name, family_name, teacher_id):
        super(Teacher, self).__init__(first_name, family_name)
        self.teacher_id = teacher_id

class Student(Human):
    def __init__(self, first_name, family_name, student_id):
        super(Student, self).__init__(first_name, family_name)
        self.student_id = student_id
  • A person delivered by human is also a human.



  • Parent class (superclass)

  • Child class (subclass)

    • It will inherit all attributes of the parent class.

    • You can add more attributes.

    • You can override the attributes inherited from the parent class in child class.

class Parent:
    def __init__(self):
        print("Calling parent constructor")
        self.value = 123
    
class Child(Parent):
    def __init__(self):
        print("Calling child constructor")
        super().__init__()
        # super(Parent,).__init__()
        # super(Parent, self).__init__()

        print(self.value)

c = Child()
Calling child constructor
Calling parent constructor
123
class Human:
    def __init__(self, first_name, family_name, gender=None, height=None, weight=None):
        self.first_name = first_name
        self.family_name = family_name
        self.set_gender(gender)
        self.set_height(height)
        self.set_weight(weight)
    
    def set_gender(self, value):
        """Method: set_gender"""
        if value == "M":
            self.gender = "Male"
        elif value == "F":
            self.gender = "Female"
        else:
            self.gender = "Secret"
            
    def get_gender(self):
        """Method: get_gender"""
        return self.gender

    def set_height(self, value):
        """Method: set_height"""
        self.height = None
        if value is not None:
            if value <= 0:
                raise ValueError("Height cannot be less than 0.")
            else:
                self.height = value
    
    def get_height(self):
        """Method: get_height"""
        return self.height
    
    def set_weight(self, value):
        """Method: set_weight"""
        self.weight = None
        if value is not None:
            if value <= 0:
                raise ValueError("Weight cannot be less than 0.")
            else:
                self.weight = value
    
    def get_weight(self):
        """Method: get_weight"""
        return self.weight
class Teacher(Human):
    def __init__(self, first_name, family_name, teacher_id):
        super(Teacher, self).__init__(first_name, family_name)
        self.set_teacher_id(teacher_id)
    
    def set_teacher_id(self, value):
        if value is not None:
            if type(value) != str:
                raise ValueError("ID should be a string.")
            else:
                self.teacher_id = value
    
    def get_teacher_id(self):
        return self.teacher_id
    
    def set_gender(self, value):
        """Method: set_gender"""
        if value == "M":
            self.gender = "Male"
        elif value == "F":
            self.gender = "Female"
        else:
            self.gender = "干你屁事"

class Student(Human):
    def __init__(self, first_name, family_name, student_id):
        super(Student, self).__init__(first_name, family_name)
        assert isinstance(student_id, str), "ID should be a string."
        self.student_id = student_id
    
    def __str__(self) -> str:
        return "Name: {} {}\nStudent ID: {}".format(self.first_name, self.family_name, self.get_student_id())
    
    def set_student_id(self, value):
        assert isinstance(value, str), "ID should be a string."
        self.student_id = value

    
    def get_student_id(self):
        return self.student_id
A = Human("John", "Doe")
B = Teacher("Bruce", "Lee", "H00066")
C = Student("Jane", "Doe", "112514087")

# Call method "get_height"
print("Height of {}: {}".format(A.first_name, A.get_height()))
# Inherit method "get_height"
print("Height of {}: {}".format(B.first_name, B.get_height()))
Height of John: None
Height of Bruce: None
# Call method "get_gender"
print("Gender of {}: {}".format(A.first_name, A.get_gender()))
# Override method "get_gender"
print("Gender of {}: {}".format(B.first_name, B.get_gender()))
Gender of John: Secret
Gender of Bruce: 干你屁事
# Call method "get_teacher_id"
for human in [A, B, C]:
    try:
        print("ID of {}: {}".format(human.first_name, human.get_teacher_id()))
    except:
        print("{} does not have teacher ID".format(human.first_name))
John does not have teacher ID
ID of Bruce: H00066
Jane does not have teacher ID
# Call method "get_student_id"
for human in [A, B, C]:
    try:
        print("ID of {}: {}".format(human.first_name, human.get_student_id()))
    except:
        print("{} does not have student ID".format(human.first_name))
John does not have student ID
Bruce does not have student ID
ID of Jane: 112514087

Exercise 9.1#

  • Please add a data attribute called grades in the child class Student. The data type of this attribute Student.grades should be a dict.

  • Create two additional methods for the Student class called set_grade and get_grade.

  • Here is your sample code:

class Student(Human):
    def __init__(self, first_name, family_name, student_id):
        super(Student, self).__init__(first_name, family_name)
        assert isinstance(student_id, str), "ID should be a string."
        self.student_id = self.set_student_id(student_id)
        # 自己想
    
    def __str__(self) -> str:
        return "Name: {} {}\nStudent ID: {}".format(self.first_name, self.family_name, self.get_student_id)
    
    def set_student_id(self, value):
        assert isinstance(value, str), "ID should be a string."
        self.student_id = value
    
    def get_student_id(self):
        return self.student_id

    # 自己想
  • It should be able to print a message like this.

import random

s1 = Student("Kitty", "Lin", "313514009")
s2 = Student("Sherry", "Kuo", "312514020")

for hw in ("HW1", "HW2", "HW3"):
    s1.set_grade(hw, random.randint(0, 100))
    s2.set_grade(hw, random.randint(0, 100))

print(s1)
print(s1.get_grade())
print("="*40)
print(s2)
print(s2.get_grade())

Name: Kitty Lin
Student ID: 313514009
{‘HW1’: 51, ‘HW2’: 53, ‘HW3’: 64}

Name: Sherry Kuo
Student ID: 312514020
{‘HW1’: 13, ‘HW2’: 55, ‘HW3’: 57}


Decorator#

  • A Python feature to add functionality to an existing code

  • It means a part of the program is going to modify another part of the program.

def print_func_name(func):
    def wrap_func():
        print("Now call function: {}".format(func.__name__))
        func()
    return wrap_func()

def xjp():
    print("我们怀念他.")

def trump():
    print("Let's make America great again!")

def tee():
    print("2024以後不關我的事.")

print_func_name(xjp)
print_func_name(trump)
print_func_name(tee)
Now call function: xjp
我们怀念他.
Now call function: trump
Let's make America great again!
Now call function: tee
2024以後不關我的事.
  • About the above code:

    1. Function can be a parameter.

    2. When we executed print_func_name(xjp)(), the function xjp was passed to the wrap function wrap_func first.

    3. Additional functionality was added by wrap_func.

    4. wrap_func was then returned by print_func_name.

    5. print_func_name is a decorator.

  • What will happen if you modify the code return wrap_func() as return wrap_func?


Syntax candy “@” of the decorator#

  • It is a syntax that keeps your code clean.

  • A special character “@

def decorator1(func):
    def wrap_func():
        print("Now call function: {}".format(func.__name__))
        func()
    return wrap_func

# Use "@"
@decorator1
def xjp():
    print("我们怀念他.")

@decorator1
def trump():
    print("Let's make America great again!")

@decorator1
def tee():
    print("2024以後不關我的事.")

xjp()
trump()
tee()
Now call function: xjp
我们怀念他.
Now call function: trump
Let's make America great again!
Now call function: tee
2024以後不關我的事.

Sequence of decorator#

  • If there are multiple decorators, Python starts executing the decorator from the nearest to farthest.

def decorator1(func):
    def wrap_func():
        print("Now call function: {}".format(func.__name__))
        func()
    return wrap_func

def decorator2(func):
    def wrap_func2():
        print("{}:".format(func.__name__))
        func()
    return wrap_func2

@decorator1
@decorator2
def trump():
    print("Let's make America great again!")

@decorator2
@decorator1
def tee():
    print("2024以後不關我的事.")

print("Test 1")
trump()
print("="*50)
print("Test 2")
tee()
Test 1
Now call function: wrap_func2
trump:
Let's make America great again!
==================================================
Test 2
wrap_func:
Now call function: tee
2024以後不關我的事.
  • About the “Test 1” of the above code:

    1. When we executed trump(), the function trump was passed to the decorator2 first.

    2. Additional functionality was added by wrap_func2 in decorator2.

    3. wrap_func2 was returned and passed to decorator1.

    4. Additional functionality was added by wrap_func in decorator1.

    5. wrap_func was returned.

    6. Python executed wrap_func, the input was wrap_func2.

    7. Python executed wrap_func2, the input was trump.


Decorator can have parameters#

def decorator3(time):
    def wrap_func(func):
        def wrap_func2(name):
            print("Now call function: {}".format(func.__name__))
            func(name)
            print("I'm a student of NYCU in {}.".format(time))
        return wrap_func2
    return wrap_func

@decorator3(2023)
def student(name):
    print("I'm {}.".format(name))

student("666")
Now call function: student
I'm 666.
I'm a student of NYCU in 2023.

class decorator#

class Person:
    def __init__(self, func):
        self.name = "stranger"
        self.status = func

    def info(self):
        print(self.name)

    def skill(self):
        return self.status()

@Person
def yelling_person():
    print("You foooooooooool!!!")

@Person
def jogging_person():
    print("He is jogging.")

p1 = yelling_person
p1.info()
p1.skill()

p2 = jogging_person
p2.info()
p2.skill()
stranger
You foooooooooool!!!
stranger
He is jogging.
Don't click this
class Student(Human):
    def __init__(self, first_name, family_name, student_id):
        super(Student, self).__init__(first_name, family_name)
        assert isinstance(student_id, str), "ID should be a string."
        self.student_id = student_id
        self.grade = {}
    
    def __str__(self) -> str:
        return "Name: {} {}\nStudent ID: {}".format(self.first_name, self.family_name, self.get_student_id())
    
    def set_student_id(self, value):
        assert isinstance(value, str), "ID should be a string."
        self.student_id = value

    def get_student_id(self):
        return self.student_id
    
    def set_grade(self, key, value):
        self.grade[key] = value
    
    def get_grade(self,):
        return self.grade