11. คลาสและแนวคิดเชิงวัตถุ¶
Object-Oriented Programming
11.1. จุดประสงค์¶
เข้าใจที่มาของคลาส
สามารถระบุองค์ประกอบและเขียนประกาศคลาสได้
สามารถเขียนคุณลักษณะของคลาสที่กำหนดให้ได้
สามารถเขียนฟังก์ชันของคลาสที่กำหนดให้ได้
11.2. Class & Object (คลาสและวัตถุ)¶
คลาสหมายถึงแม่พิมพ์ที่กำหนดคุณลักษณะและฟังก์ชันของวัตถุ
วัตถุ (Object/instance) สร้างมาจาก คลาส (Class)
Class ประกอบด้วย
คุณลักษณะ (Attributes)
ฟังก์ชัน (Functions/Methods)
ตัวอย่างเช่น
class \(\to\) อาจารย์
object \(\to\) อเปา, อวุฒิ, อเค, อต้อม
class \(\to\) นักศึกษา
object \(\to\) แบงค์, bond, great, benz
class \(\to\) คอมพิวเตอร์
object \(\to\) เครื่องหมายเลข1lab1, เครื่องหมายเลข3lab2
class \(\to\) รายวิชา
object \(\to\) progfund, discrete, foundeng
ในภาษาไพธอนข้อมูลทุกอย่างเป็น object ที่สร้างจากแม่พิมพ์(class) เช่นเมื่อเราใช้คำสั่ง
x = 20
python จะสร้าง object เพื่อเก็บค่า 20 ไว้ตามนิยามของ class ชื่อว่า
int จากนั้นจะใช้ตัวแปรชื่อ x เพื่ออ้างถึง object นี้
หรือ
a = [20, 21, 22, 21]
python จะสร้าง object เพื่อเก็บค่า 20, 21, 22, 21 ไว้ตามนิยามของ
class ชื่อว่า list จากนั้นจะใช้ตัวแปรชื่อ a เพื่ออ้างถึง object นี้
โดยแต่ละ object จะมี id ต่างกัน ดูได้จากคำสั่ง
id(x)
นอกจากนี้เรายังสามารถตรวจสอบความถูกต้องของคลาสได้โดยใช้คำสั่ง
isinstance(x, int)
เราสามารถแสดงชื่อ class ของ object ที่ตัวแปร x อ้างได้โดยใช้
x.__class__
เราสามารถแสดง attributes(คุณลักษณะ) และ functions ทั้งหมดของคลาสที่ตัวแปร x อ้างถึงได้โดยใช้
dir(x)
Exercise: จงบอกชื่อคลาสของ object ที่ตัวแปรต่อไปนี้อ้างถึง
a = 'Paul Phoenix'
b = 20
c = 3.90
d = [a, b, c]
e = (0, 0, 0)
f = True
g = a[0]
h = {2, 3, 9}
i = {}
j = {
'bond': 3.25,
'aim': 3.44,
'great': 3.25
}
k = [
[3.25, 3.22, 3.45],
[2.75, 3.00, 3.77],
[3.20, 4.00, 3.33]
]
11.3. ที่มาของคลาส¶
กำหนดระบบจัดเก็บคะแนนรวมของนักศึกษาชั้นปีที่ 1 สาขาวิทยาการคอมพิวเตอร์ตามรายวิชา 5 วิชา โดยจำลองข้อมูลต่อไปนี้
csyr1 = {
'bank' : [80, 75, 66, 77, 78],
'bond' : [82, 79, 65, 85, 82],
'aim' : [87, 68, 82, 92, 90],
'great': [81, 69, 75, 63, 92],
'เอิง' : [83, 77, 76, 67, 92],
'aom' : [70, 99, 68, 59, 76]
}
เพื่อให้ข้อมูลครบจำเป็นต้องแยกรายวิชาไว้เป็นตัวแปรต่างหากเช่น
subjects = ['discrete', 'progfund', 'eng', 'introcs', 'safety']
หรือจะใช้แบบนี้เพื่อให้สามารถใช้ตัวแปรได้เพียงตัวเดียว
csyr1 = {
'bank' : [
'discrete':80,
'progfund':75,
'eng':66,
'introcs':77,
'safety':78
],
'bond' : [
'discrete': 82,
'progfund': 79,
'eng': 65,
'introcs': 85,
'safety': 82
],
...
}
การเก็บข้อมูลทั้งสองแบบนี้ถึงแม้จะมีข้อมูลที่สมบูรณ์ * การเขียนโปรแกรมเพื่อตอบโจทย์ที่ต้องการก็ซับซ้อนทำความเข้าใจลำบาก หรือ * จำเป็นต้องมีคำสั่งที่ซ้ำซ้อนกันทำให้โปรแกรมยาวเกินความจำเป็น
11.4. ตัวอย่างโจทย์ปัญหา¶
EX1501
จงหาค่าเฉลี่ยของคะแนนรวมของแต่ละคนEX1502
วิชาไหนยากที่สุดสำหรับนักศึกษา / วิชาไหนควรแนะนำให้น้องลงEX1503
ถ้าเกณฑ์การตัดเกรด 0 - 4 เป็น F - A ตามลำด้บ และจำนวนหน่วยกิตเหมือนกันหมดทุกวิชา จงหา GPA ของแต่ละคนEX1504
โดยเฉลี่ยแล้ว GPA ของนักศึกษาวิทยาการคอมพิวเตอร์ชั้นปีที่ 1 เป็นเท่าไหร่?EX1505
ถ้าเกณฑ์การตัดเกรดของแต่ละวิชาแตกต่างกัน จงหา GPA ของแต่ละคน
11.5. ตอบโจทย์ EX1501¶
for k,v in csyr1.items():
print(k, sum(v)/len(v))
11.6. ตอบโจทย์ EX1502¶
แบบที่ 1
n = len(subjects)
sumv = [0, 0, 0, 0, 0] # [0]*n
for v in csyr1.values():
for i in range(n):
sumv[i] += v[i]
for i in range(n):
sumv[i] = (sumv[i]/n, subjects[i])
sumv.sort()
print(sumv)
แบบที่ 2
n = len(subjects)
sumv = [0]*n
for v in csyr1.values():
sumv = [ sumv[i]+v[i] for i in range(n) ]
sumv = [ (sumv[i]/n,subjects[i]) for i in range(n) ]
sumv.sort()
print(sumv)
11.7. ตอบโจทย์ EX1503¶
ตัดเกรด \(F=0,D=50,C=60,B=70,A=80\) (โดยสมมติว่าไม่มี D+, C+, B+)
def grade(s):
if s >= 80:
return 4 # A
elif s >= 70:
return 3 # B
elif s >= 60:
return 2 # C
elif s >= 50:
return 1 # D
else:
return 0 # F
def gpa(v):
return sum([ gpa(s) for s in v ])/len(v)
for k,v in csyr1.items():
print(k, gpa(v))
หมายเหตุ: ฟังก์ชัน grade(s) สามารถแทนด้วย? ช่วยอธิบายที
sum([ 1 for a in [80,70,60,50] if s > a ])
11.8. สร้างคลาสนักศึกษา Student เพื่อตอบโจทย์¶
ประกาศคลาส Student
class Student:
count = 0 # class variable ที่ทุก object ของ Student ใช้ร่วมกัน
def __init__(self): # self อ้างถึง object/instance่
self.name = ''
self.v = []
วิธีสร้าง object ของคลาส Student
bond = Student() # เรียก
bond.name = 'James Bond'
bond.v = [82, 79, 65, 85, 82]
print(bond.count) # Student.count
bond.count = 1
aim = Student()
aim.name = 'Amy Winehouse'
aim.v = [87, 68, 82, 92, 90]
print(aim.count)
11.9. การประกาศคลาสเพื่อกำหนดค่าเริ่มต้น¶
การประกาศฟังก์ชัน init(self) หรือ constructor เพื่อให้กำหนดค่าเริ่มต้นได้ด้วยสามารถทำได้ดังนี้
class Student:
count = 0
def __init__(self, name, v):
self.name = name
self.v = v
การใช้งานจะสามารถปรับได้เป็น
bond = Student('James Bond', [82, 79, 65, 85, 82])
print(bond.count) # Student.count
bond.count = 1
aim = Student('Amy Winehouse', [87, 68, 82, 92, 90])
print(aim.count)
print(aim.name)
11.10. การประกาศฟังก์ชันอื่นๆภายในคลาส¶
class Student:
count = 0
def __init__(self, name, v):
self.name = name
self.v = v
def gpa(self):
g = [sum([1 for a in [80,70,60,50] if s >= a]) for s in self.v]
return sum(g)/len(g)
การสร้างคลาสเพื่อรวมคุณลักษณะที่เกี่ยวข้องกันไว้ด้วยกัน เพื่อจำกัดสิทธิ์ในการใช้งาน นี้เราเรียกว่า การห่อหุ้มข้อมูลหรือ Encapsulation
11.10.1. การใช้งาน¶
csyr1 = [
Student('bank' , [80, 75, 66, 77, 78]),
Student('bond' , [82, 79, 65, 85, 82]),
Student('aim' , [87, 68, 82, 92, 90]),
Student('great', [81, 69, 75, 63, 92]),
Student('เอิง' , [83, 77, 76, 67, 92]),
Student('aom' , [70, 99, 68, 59, 76])
]
subjects = ['discrete', 'progfund', 'eng', 'introcs', 'safety']
for student in csyr1:
print(student.name, student.gpa())
11.11. การเขียนคลาสที่มีคุณลักษณะร่วม¶
ในบางกรณีเราอาจมีคลาสที่มีการสืบทอดคุณลักษณะหรือคุณสมบัติร่วมกันเช่น ถ้าเราต้องการเขียนคลาส อาจารย์(Lecturer) เพิ่มก็อาจจะมีคุณลักษณะร่วมคือ name เป็นต้น
class Lecturer:
def __init__(self, name, subjects):
self.name = name
self.subjects = subjects
การใช้งานก็อาจจะเป็น
อเปา = Lecturer('วิชิต สมบัติ', ['discrete', 'progfund'])
อต้อม = Lecturer('ไพชยนต์ คงไชย', ['introcs', 'progfund'])
11.11.1. การดึงคุณลักษณะร่วมมาสร้างเป็น supeclass (คลาสแม่)¶
เราอาจพิจาณาให้คลาสแม่ของอาจารย์และนักศึกษาเป็น คลาสมนุษย์(Human) โดยดึงคุณลักษณะร่วมมาไว้ที่คลาสแม่ได้ดังนี้
class Human:
def __init__(self, name):
self.name = name
class Lecturer(Human):
def __init__(self, name, subjects):
super().__init__(name)
self.subjects = subjects
class Student(Human):
count = 0
def __init__(self, name, v):
super().__init__(name)
self.v = v
def gpa(self):
g = [sum([1 for a in [80,70,60,50] if s > a]) for s in self.v]
return sum(g)/len(g)
ซึ่งเราจะเรียก Human ว่าเป็น superclass ส่วน Lecturer และ Student เป็น subclass
แนวคิดการสืบทอดคุณลักษณะ (Inheritance) นี้เป็นหนึ่งใน 3 หลักการสำคัญของ OOP
11.12. โจทย์ปัญหา¶
EX1506
แยก รายวิชาออกมาเป็นอีกคลาสชื่อ Subject โดยมีชื่อวิชาและเกณฑ์การตัดเกรด \(g=[80,70,60,50]\)EX1507
อ่านโจทย์จากไฟล์คะแนนจริงในรูปแบบ ชื่อ-สกุล,คะแนนวิชา1,คะแนนวิชา2,...EX1508
ปรับคลาสใหม่ให้เหมาะสม Lecturer ควรเปลี่ยนหรือไม่?
11.13. Polymorphism¶
เป็นหลักการสุดท้ายในแนวคิดเชิงวัตถุ ซึ่งทั้งหมดรวมกันเป็น
Polymorphism - การเป็นได้หลายรูปแบบและรองรับได้หลายรูป
Inheritance - การสืบทอดคุณสมบัติ
Encapsulation - การห่อหุ้มข้อมูล
class Human:
def __init__(self, name):
self.name = name
def work(self):
print('work work work')
class Lecturer(Human):
def __init__(self, name, subjects):
super().__init__(name)
self.subjects = subjects
def work(self):
print('lecture conference meeting')
class Student(Human):
count = 0
def __init__(self, name, v):
super().__init__(name)
self.v = v
def work(self):
print('study study party')
def gpa(self):
g = [sum([1 for a in [80,70,60,50] if s > a]) for s in self.v]
return sum(g)/len(g)
11.13.1. ฟังก์ชันที่รองรับได้หลายรูป¶
def getworking(obj):
obj.work()
bank = Student('James Mars' , [80, 75, 66, 77, 78])
อเค = Lecturer('เกรียงศักดิ์ ตรีประพิณ' , ['progfund'])
getworking(bank)
getworking(อเค)
11.13.2. object เป็นได้หลายรูป¶
lucy = Human("ป้าทวดลูซี่")
isinstance(อเค, Lecturer)
isinstance(อเค, Human)
isinstance(bank, Student)
isinstance(bank, Human)
isinstance(lucy, Human)
isinstance(bank, Lecturer)
isinstance(อเค, Student)
11.14. Everything is an Object¶
http://zetcode.com/gui/pyqt5/firstprograms/