14. คลาสและแนวคิดเชิงวัตถุ

Object-Oriented Programming

Colab

14.1. จุดประสงค์

  • เข้าใจที่มาของคลาส

  • สามารถระบุองค์ประกอบและเขียนประกาศคลาสได้

  • สามารถเขียนคุณลักษณะของคลาสที่กำหนดให้ได้

  • สามารถเขียนฟังก์ชันของคลาสที่กำหนดให้ได้

14.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]
]

14.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
    ],
...
}

การเก็บข้อมูลทั้งสองแบบนี้ถึงแม้จะมีข้อมูลที่สมบูรณ์ * การเขียนโปรแกรมเพื่อตอบโจทย์ที่ต้องการก็ซับซ้อนทำความเข้าใจลำบาก หรือ * จำเป็นต้องมีคำสั่งที่ซ้ำซ้อนกันทำให้โปรแกรมยาวเกินความจำเป็น

14.4. ตัวอย่างโจทย์ปัญหา

  • EX1501 จงหาค่าเฉลี่ยของคะแนนรวมของแต่ละคน

  • EX1502 วิชาไหนยากที่สุดสำหรับนักศึกษา / วิชาไหนควรแนะนำให้น้องลง

  • EX1503 ถ้าเกณฑ์การตัดเกรด 0 - 4 เป็น F - A ตามลำด้บ และจำนวนหน่วยกิตเหมือนกันหมดทุกวิชา จงหา GPA ของแต่ละคน

  • EX1504 โดยเฉลี่ยแล้ว GPA ของนักศึกษาวิทยาการคอมพิวเตอร์ชั้นปีที่ 1 เป็นเท่าไหร่?

  • EX1505 ถ้าเกณฑ์การตัดเกรดของแต่ละวิชาแตกต่างกัน จงหา GPA ของแต่ละคน

14.5. ตอบโจทย์ EX1501

for k,v in csyr1.items():
    print(k, sum(v)/len(v))

14.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)

14.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 ])

14.8. สร้างคลาสนักศึกษา Student เพื่อตอบโจทย์

  1. ประกาศคลาส Student

class Student:
    count = 0 # class variable ที่ทุก object ของ Student ใช้ร่วมกัน
    def __init__(self): # self อ้างถึง object/instance่
        self.name = ''
        self.v = []
  1. วิธีสร้าง 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)

14.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)

14.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

14.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())

14.11. การเขียนคลาสที่มีคุณลักษณะร่วม

ในบางกรณีเราอาจมีคลาสที่มีการสืบทอดคุณลักษณะหรือคุณสมบัติร่วมกันเช่น ถ้าเราต้องการเขียนคลาส อาจารย์(Lecturer) เพิ่มก็อาจจะมีคุณลักษณะร่วมคือ name เป็นต้น

class Lecturer:
    def __init__(self, name, subjects):
        self.name = name
        self.subjects = subjects

การใช้งานก็อาจจะเป็น

อเปา = Lecturer('วิชิต สมบัติ', ['discrete', 'progfund'])
อต้อม = Lecturer('ไพชยนต์ คงไชย', ['introcs', 'progfund'])

14.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

14.12. โจทย์ปัญหา

  • EX1506 แยก รายวิชาออกมาเป็นอีกคลาสชื่อ Subject โดยมีชื่อวิชาและเกณฑ์การตัดเกรด \(g=[80,70,60,50]\)

  • EX1507 อ่านโจทย์จากไฟล์คะแนนจริงในรูปแบบ ชื่อ-สกุล,คะแนนวิชา1,คะแนนวิชา2,...

  • EX1508 ปรับคลาสใหม่ให้เหมาะสม Lecturer ควรเปลี่ยนหรือไม่?

14.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)

14.13.1. ฟังก์ชันที่รองรับได้หลายรูป

def getworking(obj):
    obj.work()

bank = Student('James Mars' , [80, 75, 66, 77, 78])
อเค = Lecturer('เกรียงศักดิ์ ตรีประพิณ' , ['progfund'])

getworking(bank)
getworking(อเค)

14.13.2. object เป็นได้หลายรูป

lucy = Human("ป้าทวดลูซี่")

isinstance(อเค, Lecturer)
isinstance(อเค, Human)

isinstance(bank, Student)
isinstance(bank, Human)

isinstance(lucy, Human)

isinstance(bank, Lecturer)
isinstance(อเค, Student)