Thừa kế và đa hình trong Python

Xem thêm các chuyên mục:

1- Giới thiệu

Thừa kế và đa hình - đây là một khái niệm vô cùng quan trọng trong Python. Mà bạn bắt buộc phải hiểu nó.
Trước khi bắt đầu học về "Thừa kế trong Python", hãy đảm bảo rằng bạn đã có khái niệm về "Lớp và đối tượng", nếu không hãy tìm hiểu nó:
Tài liệu này được viết dựa trên:
  • Python 3.x

2- Thừa kế trong Python

Python cho phép bạn tạo một lớp mở rộng từ một hoặc nhiều lớp khác. Lớp này được gọi là lớp dẫn xuất (Derived class), hoặc đơn giản gọi là lớp con.

Lớp con sẽ thừa kế các thuộc tính (attribute), phương thức và các thành viên khác từ lớp cha. Nó cũng có thể ghi đè (override) các phương thức từ class cha. Nếu lớp con không tự định nghĩa 1 constructor của nó, mặc định nó sẽ thừa kế constructor của lớp cha.
Khác với Java, CSharp và một vài ngôn ngữ khác, Python cho phép đa thừa kế. Một lớp có thể được mở rộng (extends) từ một hoặc nhiều lớp cha.
Chúng ta cần một vài class tham gia vào minh họa.
  • Animal: Class mô phỏng một lớp Động vật.
  • Duck: Class mô phỏng lớp vịt, là một class con của Animal.
  • Cat: Class mô phỏng lớp mèo, là một class con của Animal
  • Mouse: Class mô phỏng lớp chuột, là một class con của Animal.
Trong Python, phương thức khởi tạo (constructor) sử dụng để tạo một đối tượng, và gán giá trị cho các thuộc tính (attribute).
Phương thức khởi tạo (constructor) của lớp con bao giờ cũng gọi tới phương thức khởi tạo của lớp cha để gán giá trị cho các thuộc tính của lớp cha, sau đó nó mới gán giá trị cho các thuộc tính của nó.
Hãy xem ví dụ:
animal.py
class Animal :  

    # Constructor
    def __init__(self, name):
        
        # Lớp Animal có 1 thuộc tính (attribute): 'name'.
        self.name= name 
    
    # Phương thức (method):
    def showInfo(self):
        print ("I'm " + self.name)
        
    # Phương thức (method):
    def move(self):
        print ("moving ...")
Cat là lớp con thừa kế từ lớp Animal, nó cũng có các thuộc tính (attribute) của nó.
cat.py
from animal import Animal       
        
# Lớp Cat mở rộng (extends) từ lớp Animal.
class Cat (Animal): 
    
    def __init__(self, name, age, height):
        # Gọi tới constructor của lớp cha (Animal)
        # để gán giá trị vào thuộc tính 'name' của lớp cha.
        super().__init__(name)
        
        self.age = age 
        self.height = height
    
    # Ghi đè (override) phương thức cùng tên của lớp cha.
    def showInfo(self):
        
        print ("I'm " + self.name)
        print (" age " + str(self.age))
        print (" height " + str(self.height))
catTest.py
from cat import Cat


tom = Cat("Tom", 3, 20)

print ("Call move() method")

tom.move()

print ("\n")
print ("Call showInfo() method")

tom.showInfo()
Kết quả chạy module catTest:
Điều gì đã xẩy ra khi bạn khởi tạo một đối tượng từ phương thức khởi tạo (constructor)?. Nó sẽ gọi lên phương thức khởi tạo của lớp cha như thế nào? Bạn hãy xem hình minh họa dưới đây:
Với hình minh họa trên bạn thấy rằng, phương thức khởi tạo (constructor) của lớp cha được gọi trong phương thức khởi tạo của lớp con, nó sẽ gán giá trị cho các thuộc tính (attribute) của lớp cha, sau đó các thuộc tính của lớp con cũng được gán giá trị.

3- Ghi đè phương thức

Mặc định lớp con được thừa kế các phương thức từ lớp cha, tuy nhiên lớp con có thể ghi đè (override) phương thức của lớp cha.
mouse.py
from animal import Animal       
        
# Lớp Mouse mở rộng (extends) từ lớp Animal.
class Mouse (Animal): 
    
    def __init__(self, name, age, height):
        # Gọi tới Constructor của lớp cha (Animal)
        # để gán giá trị vào thuộc tính 'name' của lớp cha.
        super().__init__(name)
        
        self.age = age 
        self.height = height
    
    # Ghi đè (override) phương thức cùng tên của lớp cha.
    def showInfo(self):
        # Gọi phương thức của lớp cha.
        super().showInfo()
        print (" age " + str(self.age))
        print (" height " + str(self.height))
    
    # Ghi đè (override) phương thức cùng tên của lớp cha.        
    def move(self):
        print ("Mouse moving ...")
Test
mouseTest.py
from mouse import Mouse


jerry = Mouse("Jerry", 3, 5)

print ("Call move() method")

jerry.move()

print ("\n")
print ("Call showInfo() method")

jerry.showInfo()

4- Phương thức trừu tượng

Khái niệm về một phương thức trừu tượng (abstract method) hoặc một lớp trừu tượng.(abstract class) có trong các ngôn ngữ như Java, C#. Nhưng nó không có một cách rõ ràng trong Python. Tuy nhiên chúng ta có cách để định nghĩa nó.
Một lớp được gọi là trừu tượng (abstract) định nghĩa ra các phương thức trừu tượng và lớp con phải ghi đè (override) các phương thức này nếu muốn sử dụng chúng. Các phương thức trừu tượng luôn ném ra ngoại lệ NotImplementedError.
abstractExample.py
# Một lớp trừu tượng (Abstract class).
class AbstractDocument :
    
    def __init__(self, name):
        
        self.name = name
        
    # Một phương thức không thể sử dụng được, vì nó luôn ném ra lỗi.
    def show(self):
        raise NotImplementedError("Subclass must implement abstract method")    
    
    

class PDF(AbstractDocument):
    
    # Ghi đè phương thức của lớp cha
    def show(self):
        print ("Show PDF document:", self.name)
        
        
class Word(AbstractDocument):     
    
    def show(self):
        print ("Show Word document:", self.name)

# ----------------------------------------------------------
documents = [ PDF("Python tutorial"),
              Word("Java IO Tutorial"),
              PDF("Python Date & Time Tutorial") ]     


for doc in documents :
    
    doc.show()
Ví dụ ở trên thể hiện tính đa hình (Polymorphism) trong Python. Một đối tượng Document (tài liệu) có thể được thể hiện ở các hình thái khác nhau ( PDF, Word, Excel, ...).
Một ví dụ khác minh họa về tính đa hình: Khi tôi nói về một người Châu Á, nó khá trừu tượng, anh ta có thể là một người Nhật Bản, người Việt Nam, hoặc một người Ấn Độ, ... Tuy nhiên đều có đặc điểm đặc trưng của người Châu Á.

5- Đa thừa kế

Python cho phép đa thừa kế, điều đó có nghĩa là bạn có thể tạo ra một lớp mở rộng (extends) từ 2 hoặc nhiều lớp khác. Các lớp cha có thể có các thuộc tính (attribute) giống nhau, hoặc các phương thức giống nhau,.... Lớp con sẽ ưu tiên thừa kế thuộc tính, phương thức, ... của lớp đứng đầu tiên trong danh sách thừa kế.
multipleInheritanceExample.py
class Horse:
    
    maxHeight = 200; # centimeter
    
    def __init__(self, name, horsehair):
        self.name = name
        self.horsehair = horsehair
    
    def run(self):
        print ("Horse run")   
     
    def showName(self):
        print ("Name: (House's method): ", self.name)   
        
    def showInfo(self):
        print ("Horse Info")    
 

class Donkey:
    
    def __init__(self, name, weight):        
        self.name = name
        self.weight = weight    
        
        
    def run(self):
        print ("Donkey run")     
        
    def showName(self):        
        print ("Name: (Donkey's method): ", self.name)   

    def showInfo(self):
        print ("Donkey Info")               
  
# Lớp Mule thừa kế từ Horse và Donkey.
class Mule(Horse, Donkey):
    
    def __init__(self, name, hair, weight): 
        Horse.__init__(self, name, hair)  
        Donkey.__init__(self, name, weight)
        
    
    def run(self):   
        print ("Mule run")      


    def showInfo(self):
        print ("-- Call Mule.showInfo: --")
        Horse.showInfo(self)
        Donkey.showInfo(self)

# ---- Test ------------------------------------
# Biến 'maxHeight', được thừa kế từ lớp Horse.
print ("Max height ", Mule.maxHeight)

mule = Mule("Mule", 20, 1000)

mule.run()

mule.showName() 

mule.showInfo()

Phương thức mro()

Phương thức mro() giúp bạn xem danh các lớp cha của một lớp nào đó. Hãy xem một ví dụ dưới đây:
mroExample.py
class X: pass
class Y: pass
class Z: pass

class A(X,Y): pass
class B(Y,Z): pass

class M(B,A,Z): pass

# Output:
# [<class '__main__.M'>, <class '__main__.B'>,
# <class '__main__.A'>, <class '__main__.X'>,
# <class '__main__.Y'>, <class '__main__.Z'>,
# <class 'object'>]

print(M.mro())
Chú ý: Trong Python, lệnh pass (pass statement) giống như một lệnh null (hoặc rỗng), nó không làm gì cả, nếu lớp hoặc phương thức không có nội dung, bạn vẫn phải cần ít nhất một lệnh, hãy sử dụng pass.

6- Hàm issubclass và isinstance

Python có 2 hàm hữu ích:
  • isinstance
  • issubclass
     

isinstance

Hàm isinstance giúp bạn kiểm tra xem một "cái gì đó" có phải là một đối tượng của một lớp nào đó hay không.

issubclass

Hàm issubclass kiểm tra xem lớp này có phải là con của một lớp khác hay không.
isinstancesubclass.py
class A: pass
class B(A): pass


# True
print ("isinstance('abc', object): ",isinstance('abc', object)) 

# True
print ("isinstance(123, object): ",isinstance(123, object))


b = B()
a = A()

# True
print ("isinstance(b, A): ", isinstance(b, A) )
print ("isinstance(b, B): ", isinstance(b, B) )

# False
print ("isinstance(a, B): ", isinstance(a, B) )


# B is subclass of A? ==> True
print ("issubclass(B, A): ", issubclass(B, A) )

# A is subclass of B? ==> False
print ("issubclass(A, B): ", issubclass(A, B) )

7- Đa hình với hàm

Ở đây tôi tạo ra 2 lớp EnglishFrench. Cả hai lớp này đều có phương thức greeting(). Cả hai tạo ra các lời chào khác nhau. Tạo ra 2 đối tượng tương ứng từ 2 lớp trên và gọi hành động của 2 đối tượng này trong cùng một hàm (Hàm intro)
people.py

class English:
   
    def greeting(self):       
        print ("Hello")
       
       
class French:
   
    def greeting(self):
        print ("Bonjour")
 
 
def intro(language):               
   
    language.greeting()
   
   
flora  = English()
aalase = French()   


intro(flora)
intro(aalase)
Chạy ví dụ:

Xem thêm các chuyên mục: