Python – Inheritance

การสืบทอด(Inheritance)
การสืบทอดเป็นคุณลักษณะเด่นอย่างหนึ่งสำหรับการเขียนโปรแกรมแบบ OO(Object-Oriented Programming) โดยอ้างถึงการกำหนดคลาสขึ้นมาใหม่และด้วยคุณลักษณะจากคลาสที่มีอยู่โดยไม่มีการแก้ไขใดๆ โดยคลาสใหม่เรียกว่าคลาสสืบทอด(derived or child class) และคลาสที่เป็นต้นแบบของการสืบทอดนั้นเรียกว่าคลาส base(หรือ parent)

class BaseClass:
     # รายละเอียดของ Base Class

class DerivedClass(BaseClass):
     # รายละเอียดของ Derived Class

คลาสสืบทอด(derived class) ได้สืบทอดคุณลักษณะจากคลาสต้นแบบ และสามารถเพิ่มคุณลักษณะใหม่ลงไปได้ ซึ่งทำให้เราสามารถสร้างคลาสใหม่โดยการนำคุณลักษณะเดิมที่มีอยู่แล้วมาปรับปรุงเพื่อใช้งานได้มากขึ้น

ตัวอย่างการสืบทอด
กำหนดให้ polygon เป็นรูปปิดซึ่งมีด้าง 3 ด้าน เราทำการสร้างคลาส polygon ได้ดังนี้

class Polygon:
    def __init__(self, no_of_sides):
        self.n = no_of_sides
        self.sides = [0 for i in range(no_of_sides)]

    def inputSides(self):
        self.sides = [float(input("Enter side "+str(i+1)+" : ")) for i in range(self.n)]

    def dispSides(self):
        for i in range(self.n):
            print("Side",i+1,"is",self.sides[i])

โดยคลาสจะมีข้อมูลของแอทริบิวท์เพื่อเก็บจำนวนของด้านโดย n และ magnitude ของแต่ละด้านทำการเก็บข้อมุลในลิสต์ชื่อ sides

ฟังก์ชัน InputSides() เราเอาข้อมูล magnitude ของแต่ละด้านและฟังก์ชัน dispSides() จะทำการแสดงข้อมูลดังกล่าว

โดยสามเหลี่ยมก็เป็นรูปแบบหนึ่งของ polygon ซึ่งประกอบด้วยข้อมูลของด้าน 3 ด้าน ดังนั้นเราสามารถจะสร้างคลาสชื่อว่า Triangle ซึ่งสืบทอดคุณลักษณะจาก polygon ซึ่งจะทำให้แอทริบิวท์ทั้งหมดที่มีในคลาส Polygon สามารถจะใช้ได้ในคลาส Triangle โดยไม่ต้องเขียนโค้ดใหม่เพื่อระบุ

class Triangle(Polygon):
    def __init__(self):
        Polygon.__init__(self,3)

    def findArea(self):
        a, b, c = self.sides
        # calculate the semi-perimeter
        s = (a + b + c) / 2
        area = (s*(s-a)*(s-b)*(s-c)) ** 0.5
        print('The area of the triangle is %0.2f' %area)

ในคลาส Triangle เราได้ทำการเพิ่มฟังก์ชันใหม่คือ findArea() เพื่อจะทำการคำนวณและพิมพ์ค่าขนาดของพื้นที่ของสามเหลี่ยม

t = Triangle()
t.inputSides()
#Enter side 1: 3 
#Enter side 2: 4 
#Enter side 3: 5 

t.dispSides()
Side 1 is 3.0
Side 2 is 4.0
Side 3 is 5.0

t.findArea()
The area of triangle is 6.0

จากตัวอย่าง จะเห็นว่า แม้ว่าเราไม่ได้ทำการกำหนดเมทธอด inputSides() และ dispSides() สำหรับคลาส Triangle แต่เราก็สามารถเรียกใช้งานได้ จากการสืบทอดมาจากคลาส Polygon

ถ้าแอทริบิวท์ที่เรียกใช้งานไม่พบอยู่ในคลาสที่ระบุ จึงทำการค้นหาต่อไปยังคลาสต้นแบบ(base class) และหากคลาสดังกล่าว สืบทอดมาจากคลาสอื่นอีกทีหนึ่ง ก็จะทำเช่นนี้ไปเรื่อยๆ

การ Overriding เมทธอด
จากตัวอย่างที่แล้ว สังเกตุได้ว่า เมทธอด __init__() ถูกกำหนดรายละเอียดในคลาสทั้งในคลาส Polygon และ Triangle เมื่อเกิดกรณีเช่นนี้เมทธอดที่ระบุในคลาสสืบทอด(derived class) จะระบุรายละเอียดให้ใหม่(override) จากรายละเอียดจากคลาสต้นแบบ ซึ่งก็คือคลาส __init__ ใน Triangle ได้ทำการระบุรายละเอียดของฟังก์ชันนี้ใหม่แทนการใช้งานจากฟังก์ชัน __init__ จากคลาส Polygon

โดยส่วนใหญ่เราจะใช้การ overriding นี้เพื่อจะเพิ่มความสามารถให้เมทธอดมากกว่าที่จะเขียนความสามารถแทนที่ลงไปหมด การทำแบบนี้จะทำได้โดยการเรียกใช้งานฟังก์ชันจากคลาสต้นแบบจากฟังก์ชันชื่อเดียวกันภายในคลาสสืบทอด(การเรียก Polygon.__init__() จาก Triangle.__init__())

อีกทางเลือกที่ดีกว่าคือการใช้ built-in ฟังก์ชัน super().__init__(3) ซึ่งก็เทียบเท่ากับเราเรียกใช้ Polygon(self, 3) ซึ่งการเรียกใช้งานแบบนี้ค่อนข้างจะเป็นที่นิยมมากว่า

ยังมี built-in ฟังก์ชัน isinstance() และ issubclass() ซึ่งถูกใช้เพื่อจะตรวจสอบการสืบทอดนั้นๆ โดยฟังก์ชัน isinstance() จะคืนค่าเป็นจริงก็ต่อเมื่อออบเจคนั้นเป็น instance หนึ่งของคลาสนั้น หรือคลาสอื่นที่สืบทอดมาจากคลาสนั้นอีกที โดยทุกคลาสในไพธอนจะสืบทอดมาจากคลาสต้นแบบ object เสมอ

t = Triangle()

isinstance(t, Triangle)
# True

isinstance(t, Polygon)
# True

isinstance(t, int)
# False

isinstance(t, object)
# True

ในทำนองเดียวกัน ฟังก์ชัน issubclass() ใช้สำหรับตรวจสอบการสืบทอดของคลาสเช่นกัน

t = Triangle()

issubclass(Polygon, Triangle)
# False

issubclass(Triangle, Polygon)
# True

issubclass(bool, int)
# True