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
Instance attributes
Method attributes
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 classStudent
. The data type of this attributeStudent.grades
should be adict
.Create two additional methods for the
Student
class calledset_grade
andget_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:
Function
can be a parameter.When we executed
print_func_name(xjp)()
, the functionxjp
was passed to the wrap functionwrap_func
first.Additional functionality was added by
wrap_func
.wrap_func
was then returned byprint_func_name
.print_func_name
is a decorator.
What will happen if you modify the code
return wrap_func()
asreturn 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:When we executed
trump()
, the functiontrump
was passed to thedecorator2
first.Additional functionality was added by
wrap_func2
indecorator2
.wrap_func2
was returned and passed todecorator1
.Additional functionality was added by
wrap_func
indecorator1
.wrap_func
was returned.Python executed
wrap_func
, the input waswrap_func2
.Python executed
wrap_func2
, the input wastrump
.
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