Bài 2 · Nâng cao · 26 phút· Cập nhật 11/06/2026
Kế thừa, đa hình & lớp trừu tượng
Biên soạn bởi Nguyễn Anh Tuấn
Bốn trụ cột OOP trong Python: kế thừa (super, override), đa hình & duck typing, lớp trừu tượng (abc) và đóng gói - cùng MRO, Enum.
Bài trước nhìn OOP qua mô hình dữ liệu (dunder, property, dataclass). Bài này là bốn trụ cột OOP “cổ điển”. Đầu tiên: kế thừa - lớp con dùng lại lớp cha rồi ghi đè (override) phần khác; super() gọi ngược lên cha để khỏi chép code.
ke_thua.py
class DongVat:
def __init__(self, ten):
self.ten = ten
def keu(self):
return "..."
def gioi_thieu(self):
return f"{self.ten} keu {self.keu()}"
class Cho(DongVat):
def keu(self): # GHI DE (override)
return "Gau gau"
class Meo(DongVat):
def __init__(self, ten, mau):
super().__init__(ten) # goi __init__ cua cha, khoi chep
self.mau = mau
def keu(self):
return "Meo meo"
print(Cho("Lu").gioi_thieu()) # Lu keu Gau gau
print(Meo("Mun", "den").gioi_thieu()) Kết quả khi chạy
Lu keu Gau gau Mun keu Meo meo
- ▸Lớp con kế thừa thuộc tính/phương thức của cha; chỉ viết phần KHÁC.
- ▸Override: định nghĩa lại một phương thức ở lớp con để đổi hành vi.
- ▸super() gọi phiên bản của lớp cha - đặc biệt cần trong __init__ để cha khởi tạo phần của nó.
Đừng lạm dụng kế thừa sâu
Đa hình (polymorphism): cùng một lời gọi (.dien_tich()) chạy đúng cài đặt của từng loại. Python theo duck typing: “nếu nó kêu như vịt thì coi như vịt” - chỉ cần object có phương thức cần dùng, KHÔNG bắt buộc cùng lớp cha.
da_hinh.py
class HinhVuong:
def __init__(self, c): self.c = c
def dien_tich(self): return self.c * self.c
class HinhTron:
def __init__(self, r): self.r = r
def dien_tich(self): return 3.14159 * self.r ** 2
# Hai lop KHONG chung cha - chi can deu co .dien_tich()
hinh = [HinhVuong(2), HinhTron(3)]
print(sum(h.dien_tich() for h in hinh)) # goi dung phuong thuc cua tung loai Kết quả khi chạy
32.27431
- ▸Đa hình = một lời gọi, nhiều hành vi tuỳ loại object.
- ▸Duck typing: Python quan tâm object CÓ phương thức gì, không phải nó thuộc lớp nào.
- ▸Thêm loại hình mới chỉ cần có .dien_tich() - vòng lặp xử lý không phải sửa.
Python cho phép đa kế thừa. Khi đó “gọi phương thức nào / super() đi đâu” được quyết bởi MRO (Method Resolution Order) - một thứ tự tính bằng thuật toán C3, xem qua __mro__.
mro.py - kim cuong
class A:
def chao(self): return "A"
class B(A):
def chao(self): return "B -> " + super().chao()
class C(A):
def chao(self): return "C -> " + super().chao()
class D(B, C):
pass
print(D().chao()) # B -> C -> A
print([k.__name__ for k in D.__mro__]) # ['D', 'B', 'C', 'A', 'object'] Kết quả khi chạy
B -> C -> A ['D', 'B', 'C', 'A', 'object']
Trung thực: super() KHÔNG phải “cha trực tiếp”
Khi muốn ép mọi lớp con phải cài một số phương thức, dùng lớp trừu tượng: kế thừa abc.ABC và đánh dấu @abstractmethod. Lớp còn thiếu method thì không tạo được object.
abc_demo.py
from abc import ABC, abstractmethod
class Hinh(ABC):
@abstractmethod
def dien_tich(self): ... # chi la "hop dong", khong cai dat
class Vuong(Hinh):
def __init__(self, c): self.c = c
def dien_tich(self): return self.c * self.c
print(Vuong(3).dien_tich()) # 9
# Hinh() -> TypeError: Can't instantiate abstract class Hinh
# class Thieu(Hinh): pass; Thieu() -> cung loi vi chua cai dien_tich() Kết quả khi chạy
9
- ▸abc.ABC + @abstractmethod = “hợp đồng”: lớp con BẮT BUỘC cài đủ.
- ▸Không thể tạo object từ lớp trừu tượng (hay lớp con còn thiếu method) → lỗi ngay.
- ▸Khác duck typing (không ràng buộc) và Protocol (structural, không cần kế thừa).
Phương thức thường nhận self (một object cụ thể). @classmethod nhận cls (chính class) - hợp làm constructor thay thế. @staticmethod không nhận gì cả - chỉ là hàm tiện ích gom vào class cho gọn.
class_static.py
class Ngay:
def __init__(self, d, m, y):
self.d, self.m, self.y = d, m, y
@classmethod
def tu_chuoi(cls, s): # constructor thay the; cls = chinh class
d, m, y = map(int, s.split("/"))
return cls(d, m, y) # dung cls -> ke thua van dung dung lop con
@staticmethod
def la_nam_nhuan(y): # khong self/cls - chi la tien ich
return y % 4 == 0 and (y % 100 != 0 or y % 400 == 0)
ng = Ngay.tu_chuoi("2/9/1945")
print(ng.y, Ngay.la_nam_nhuan(2024), Ngay.la_nam_nhuan(1900)) Kết quả khi chạy
1945 True False
- ▸instance method (self): thao tác trên một đối tượng cụ thể.
- ▸@classmethod (cls): dùng class - lý tưởng cho constructor thay thế, kế thừa đúng.
- ▸@staticmethod: không self/cls - hàm độc lập nhưng liên quan, đặt trong class cho gọn.
Python không có “private” thật. Quy ước: một gạch dưới _x nghĩa là “nội bộ, đừng đụng từ ngoài”. Hai gạch dưới __x kích hoạt name mangling - Python đổi tên thành _TênLớp__x để tránh trùng khi kế thừa.
dong_goi.py
class TaiKhoan:
def __init__(self, so_du):
self._chu = "an" # _x: QUY UOC noi bo (Python khong chan)
self.__so_du = so_du # __x: doi ten -> _TaiKhoan__so_du
@property
def so_du(self): # kiem soat truy cap qua property
return self.__so_du
def nap(self, tien):
if tien <= 0: raise ValueError("phai > 0")
self.__so_du += tien
t = TaiKhoan(100)
t.nap(50); print(t.so_du) # 150
# t.__so_du -> AttributeError (da bi doi ten)
print(t._TaiKhoan__so_du) # 150 (van truy cap duoc -> khong phai bao mat) Kết quả khi chạy
150 150
“We are all consenting adults”
Khi một biến chỉ nhận vài giá trị cố định (trạng thái, màu, hướng…), đừng dùng chuỗi "cho"/"xong" hay số rời - dễ gõ sai mà không báo lỗi. Dùng Enum: gom thành nhóm có tên, an toàn và rõ nghĩa.
enum_demo.py
from enum import Enum, auto
class TrangThai(Enum):
CHO = auto() # 1
DANG_LAM = auto() # 2
XONG = auto() # 3
print(TrangThai.XONG) # TrangThai.XONG
print(TrangThai.XONG.name, TrangThai.XONG.value) # XONG 3
def mo_ta(t):
if t is TrangThai.XONG: # so sanh bang 'is' -> ro & an toan
return "Hoan tat"
return "Chua xong"
print(mo_ta(TrangThai.XONG)) # Hoan tat
# TrangThai.XONGG -> AttributeError ngay (go sai bao loi luon) Kết quả khi chạy
TrangThai.XONG XONG 3 Hoan tat
- ▸Enum gom các giá trị cố định thành nhóm có tên → an toàn hơn chuỗi/số rời.
- ▸Gõ sai tên thành viên → lỗi NGAY (chuỗi gõ sai thì im lặng chạy sai).
- ▸So sánh bằng is; auto() tự đánh số; IntEnum khi cần so sánh như số.
Tiếp theo
Câu hỏi thường gặp
Dùng KẾ THỪA khi thật sự “is-a” (Mèo LÀ Động vật) và muốn chia sẻ phần cài đặt. Dùng DUCK TYPING (hoặc Protocol ở bài type hints) khi chỉ cần “object có phương thức X” - không quan tâm dòng dõi. Nguyên tắc chung: ưu tiên GHÉP (composition) hơn kế thừa sâu; kế thừa nhiều tầng dễ rối.
Theo MRO (Method Resolution Order) - thứ tự Python tính bằng thuật toán C3, KHÔNG phải “cha viết đầu tiên”. Xem bằng Class.__mro__. super() chuyển tiếp tới lớp KẾ TIẾP trong MRO, nhờ vậy mỗi lớp trong cây chạy đúng một lần (giải quyết “diamond problem”).
ABC (abc.ABC + @abstractmethod) RÀNG BUỘC lúc tạo object: lớp con quên viết abstractmethod thì KHÔNG tạo được - và phải kế thừa tường minh. Protocol (bài type hints) là “structural”: khớp theo hình dạng, không cần kế thừa, kiểm lúc gõ kiểu. Duck typing thuần thì không ràng buộc gì, lỗi chỉ lộ lúc chạy.
classmethod nhận cls (chính class) → hợp làm “constructor thay thế” (vd Ngay.tu_chuoi("2/9/1945")) và chạy đúng cả khi bị kế thừa. staticmethod KHÔNG nhận self/cls → chỉ là một hàm tiện ích gom vào trong class cho gọn về mặt tổ chức. Còn instance method thường nhận self (thao tác trên một đối tượng cụ thể).
Không. _x là QUY ƯỚC (“nội bộ, đừng đụng từ ngoài”), Python không chặn. __x kích hoạt “name mangling”: đổi tên thành _TênLớp__x để tránh trùng tên khi kế thừa - vẫn truy cập được qua tên đã đổi. Triết lý Python: “we are all consenting adults”. Muốn KIỂM SOÁT truy cập thì dùng @property (bài “OOP & mô hình dữ liệu”).
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Trong cây kim cương class D(B, C), trong B câu super().chao() sẽ gọi tới đâu?
- 1
Kế thừa & super()
Viết
NhanVien(ten, luong)vớitinh_luong();QuanLykế thừa, overridetinh_luong()= lương + thưởng, gọisuper().tinh_luong().Hoàn thành khi:
QuanLydùng lại logic củaNhanVienquasuper(), chỉ thêm phần thưởng - không chép lại. - 2
Đa hình không if-theo-loại
Tạo list gồm
HinhVuong,HinhTron,HinhChuNhat(mỗi lớp códien_tich()). Tính TỔNG diện tích bằng MỘT vòng lặp, không dùngif/isinstancetheo loại.Hoàn thành khi:
sum(h.dien_tich() for h in hinh)chạy đúng nhờ đa hình - thêm hình mới không phải sửa vòng lặp. - 3
Đọc MRO
Dựng kim cương
A; B(A); C(A); D(B, C). InD.__mro__và giải thích vì sao thứ tự làD→B→C→A→object.Hoàn thành khi: Giải thích được C3/MRO và vì sao
super()trongBlại dẫn sangC(không phảiA). - 4
Lớp trừu tượng (ABC)
Định nghĩa
Kho(ABC)với@abstractmethodluu(x)vàdoc(); viếtKhoBoNhocài đủ; rồi thử một lớp THIẾUdoc()→ tạo object xem lỗi gì.Hoàn thành khi:
KhoBoNhochạy được; lớp thiếu method bịTypeError“Can’t instantiate abstract class…”. - 5
classmethod & staticmethod
Thêm cho lớp
Ngay: classmethodhom_nay()(dùngcls) và staticmethodla_nam_nhuan(y).Hoàn thành khi:
Ngay.hom_nay()trả về mộtNgay;Ngay.la_nam_nhuan(2024)→True,(1900)→False. - 6
Đổi chuỗi sang Enum
Lấy một biến trạng thái dạng chuỗi (
"cho"/"dang_lam"/"xong"), đổi sang EnumTrangThai; so sánh bằngis; thử gõ sai tên thành viên xem lỗi.Hoàn thành khi: So sánh bằng
isrõ nghĩa; truy cập thành viên không tồn tại →AttributeErrorngay (an toàn hơn chuỗi).