Sitelet https://www.datacamp.com/vi/tutorial/super-multiple-inheritance-diamond-problem
Chuyển đến nội dung chính

Nhập môn Kế thừa đa lớp & super()

Hướng dẫn nhập môn cho Pythonista về kế thừa đa lớp, hàm super(), và cách điều hướng bài toán hình kim cương.
Đã cập nhật 5 thg 10, 2026  · 8 phút đọc

Khám phá cùng AI

ChatGPTClaudePerplexity

Tổng quan nhanh về kế thừa

Khi dự án và gói Python của bạn lớn dần, bạn sẽ sớm muốn sử dụng lớp và áp dụng nguyên tắc DRY (đừng lặp lại chính mình). Kế thừa lớp là cách tuyệt vời để tạo một lớp dựa trên lớp khác nhằm giữ mọi thứ gọn gàng theo DRY. Bài viết này đề cập đến các khái niệm nâng cao hơn của kế thừa, nên phần cơ bản sẽ không đi quá sâu. Chúng ta sẽ có phần giới thiệu ngắn, còn các phần nhập môn chi tiết hơn bạn có thể xem ở nơi khác. Dưới đây là một số tài nguyên để bắt đầu: khóa học Lập trình Hướng đối tượng trong Python & Python Object-Oriented Programming (OOP): Hướng dẫn.

Vậy kế thừa lớp là gì? Tương tự di truyền học, một lớp con có thể “kế thừa” thuộc tính và phương thức từ lớp cha. Hãy đi thẳng vào ví dụ mã. Trong khối mã dưới đây, chúng ta minh họa kế thừa với lớp Child kế thừa từ lớp Parent.

Input

class Parent:
    def __init__(self):
        self.parent_attribute = 'I am a parent'

    def parent_method(self):
        print('Back in my day...')


# Create a child class that inherits from Parent
class Child(Parent):
    def __init__(self):
        Parent.__init__(self)
        self.child_attribute = 'I am a child'


# Create instance of child
child = Child()

# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()

Output

I am a child
I am a parent
Back in my day...

Ta thấy lớp Child đã “kế thừa” thuộc tính và phương thức từ lớp Parent. Không cần làm gì thêm, Parent.parent_method đã là một phần của lớp Child. Để tận dụng được phương thức Parent.__init__() ta cần gọi tường minh phương thức đó và truyền self. Lý do là khi ta thêm phương thức __init__ vào Child, ta đã ghi đè __init__ được kế thừa.

Với phần giới thiệu rất ngắn gọn này, hãy đi vào trọng tâm của bài viết.

Giới thiệu về super

Ở trường hợp đơn giản nhất, hàm super có thể thay thế lời gọi tường minh tới Parent.__init__(self). Ví dụ nhập môn ở phần đầu có thể viết lại với super như dưới đây. Lưu ý, khối mã dưới đây viết cho Python 3, các phiên bản trước dùng cú pháp hơi khác. Ngoài ra, phần đầu ra được lược bỏ vì giống hệt khối mã đầu tiên.

class Parent:
    def __init__(self):
        self.parent_attribute = 'I am a parent'

    def parent_method(self):
        print('Back in my day...')


# Create a child class that inherits from Parent
class Child(Parent):
    def __init__(self):
        super().__init__()
        self.child_attribute = 'I am a parent'


# Create instance of child
child = Child()

# Show attributes and methods of child class
print(child.child_attribute)
print(child.parent_attribute)
child.parent_method()

Thành thật mà nói, super trong trường hợp này mang lại rất ít, nếu có, lợi thế. Tùy tên lớp cha, ta có thể tiết kiệm vài ký tự, và không phải truyền self khi gọi __init__. Dưới đây là một số ưu nhược điểm khi dùng super trong kế thừa đơn.

Nhược điểm

Có thể lập luận rằng dùng super ở đây làm mã kém tường minh. Làm mã kém tường minh đi ngược lại Thiền Python, trong đó nêu: "Rõ ràng tốt hơn mập mờ."

Ưu điểm

Có một lập luận về khả năng bảo trì ngay cả trong kế thừa đơn. Nếu vì lý do nào đó lớp con của bạn thay đổi mẫu kế thừa (ví dụ: đổi lớp cha hoặc chuyển sang kế thừa đa lớp) thì bạn không cần tìm và thay mọi tham chiếu còn sót tới ParentClass.method_name(); việc dùng super sẽ giúp mọi thay đổi lan tỏa theo thay đổi trong câu lệnh class.

super và kế thừa đa lớp

Trước khi đi vào kế thừa đa lớp và super... Cảnh báo: phần này có thể khá kỳ lạ và phức tạp.

Đầu tiên, kế thừa đa lớp là gì? Từ nãy đến giờ, ví dụ chỉ có một lớp con kế thừa từ một lớp cha. Với kế thừa đa lớp, có nhiều hơn một lớp cha. Một lớp con có thể kế thừa từ 2, 3, 10, v.v. lớp cha.

Đây là nơi lợi ích của super trở nên rõ ràng hơn. Ngoài việc tiết kiệm vài phím khi không phải tham chiếu tên từng lớp cha, còn có những lợi ích tinh tế khi dùng super với các mẫu kế thừa đa lớp. Nói ngắn gọn, nếu bạn định dùng kế thừa đa lớp, hãy dùng super.

Kế thừa đa lớp không dùng super

Hãy xem một ví dụ kế thừa đa lớp mà không sửa đổi bất kỳ phương thức nào của lớp cha, nhờ đó không cần dùng super.

Input

class B:
    def b(self):
        print('b')


class C:
    def c(self):
        print('c')


class D(B, C):
    def d(self):
        print('d')


d = D()
d.b()
d.c()
d.d()

Output

b
c
d

Thứ tự phân giải phương thức (MRO)

Kết quả này không quá bất ngờ với khái niệm kế thừa đa lớp. D kế thừa các phương thức x và z từ các lớp cha, và mọi thứ đều ổn... tạm thời.

Vậy nếu cả B và C đều có một phương thức trùng tên thì sao? Đây là lúc một khái niệm gọi là “thứ tự phân giải phương thức” xuất hiện, viết tắt là MRO. MRO của lớp con quyết định Python sẽ tìm phương thức ở đâu và phương thức nào sẽ được gọi khi có xung đột.

Hãy xem ví dụ.

Input

class B:
    def x(self):
        print('x: B')


class C:
    def x(self):
        print('x: C')


class D(B, C):
    pass


d = D()
d.x()
print(D.mro())

Output

x: B
[<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]

Khi gọi phương thức kế thừa x, ta chỉ thấy đầu ra từ B. Ta có thể xem MRO của lớp D bằng cách gọi phương thức lớp mro. Từ kết quả D.mro() ta biết: chương trình sẽ thử gọi phương thức của D trước, sau đó đến B, rồi C, và cuối cùng là object. Nếu không tìm thấy ở bất kỳ đâu trong số đó, ta sẽ nhận lỗi rằng D không có phương thức được yêu cầu.

Đáng lưu ý là theo mặc định, mọi lớp đều kế thừa từ object, và nó nằm ở cuối mỗi MRO.

Kế thừa đa lớp, super, và bài toán hình kim cương

Dưới đây là ví dụ dùng super để xử lý MRO của init theo cách có lợi. Trong ví dụ, ta tạo một loạt lớp xử lý văn bản và kết hợp chức năng của chúng trong một lớp khác bằng kế thừa đa lớp. Ta sẽ tạo 4 lớp, và cấu trúc kế thừa sẽ theo sơ đồ dưới đây.

Lưu ý: Cấu trúc này chỉ nhằm minh họa; nếu không bị ràng buộc, sẽ có những cách triển khai hợp lý hơn.

diamond problem

Đây thực chất là ví dụ về “bài toán hình kim cương” trong kế thừa đa lớp. Tên gọi đến từ hình dạng sơ đồ và việc đây là một vấn đề khá rối rắm.

Dưới đây là phần triển khai sử dụng super.

Input

class Tokenizer:
    """Tokenize text"""
    def __init__(self, text):
        print('Start Tokenizer.__init__()')
        self.tokens = text.split()
        print('End Tokenizer.__init__()')


class WordCounter(Tokenizer):
    """Count words in text"""
    def __init__(self, text):
        print('Start WordCounter.__init__()')
        super().__init__(text)
        self.word_count = len(self.tokens)
        print('End WordCounter.__init__()')


class Vocabulary(Tokenizer):
    """Find unique words in text"""
    def __init__(self, text):
        print('Start init Vocabulary.__init__()')
        super().__init__(text)
        self.vocab = set(self.tokens)
        print('End init Vocabulary.__init__()')


class TextDescriber(WordCounter, Vocabulary):
    """Describe text with multiple metrics"""
    def __init__(self, text):
        print('Start init TextDescriber.__init__()')
        super().__init__(text)
        print('End init TextDescriber.__init__()')


td = TextDescriber('row row row your boat')
print('--------')
print(td.tokens)
print(td.vocab)
print(td.word_count)

Output

Start init TextDescriber.__init__()
Start WordCounter.__init__()
Start init Vocabulary.__init__()
Start Tokenizer.__init__()
End Tokenizer.__init__()
End init Vocabulary.__init__()
End WordCounter.__init__()
End init TextDescriber.__init__()
--------
['row', 'row', 'row', 'your', 'boat']
{'boat', 'your', 'row'}
5

Trước hết, ta thấy lớp TextDescriber đã kế thừa mọi thuộc tính trong “cây gia đình” các lớp. Nhờ kế thừa đa lớp, ta có thể “kết hợp” chức năng của nhiều lớp.

Giờ hãy bàn về các dòng in ra từ các phương thức init của lớp:

Mỗi phương thức __init__ được gọi một lần và chỉ một lần.

Lớp TextDescriber kế thừa từ 2 lớp đều kế thừa từ Tokenizer. Tại sao Tokenizer.__init__ không bị gọi hai lần?

Nếu ta thay tất cả lời gọi super bằng cách gọi kiểu cũ, ta sẽ có 2 lần gọi Tokenizer.__init__. Các lời gọi super “suy nghĩ” theo mẫu của ta kỹ càng hơn và bỏ qua chuyến đi thừa về A.

Mỗi phương thức __init__ bắt đầu trước khi bất kỳ phương thức nào khác kết thúc.

Thứ tự bắt đầu và kết thúc của từng __init__ đáng lưu ý nếu bạn định thiết lập một thuộc tính trùng tên với lớp cha khác. Thuộc tính sẽ bị ghi đè và có thể gây rối.

Trong trường hợp này, ta tránh xung đột tên với các thuộc tính kế thừa, nên mọi thứ hoạt động như mong đợi.

Nhắc lại, bài toán hình kim cương có thể trở nên phức tạp rất nhanh và dẫn đến kết quả bất ngờ. Như đa số trường hợp trong lập trình, tốt nhất nên tránh các thiết kế rắc rối.

Chúng ta đã học gì

  • Ta đã học về hàm super và cách dùng nó để thay thế ParentName.method trong kế thừa đơn. Đây có thể là thực hành dễ bảo trì hơn.
  • Ta đã học về kế thừa đa lớp và cách truyền tiếp chức năng của nhiều lớp cha vào một lớp con duy nhất.
  • Ta đã học về thứ tự phân giải phương thức (MRO) và cách nó quyết định điều gì xảy ra trong kế thừa đa lớp khi có xung đột tên giữa các phương thức lớp cha.
  • Ta đã học về bài toán hình kim cương và thấy ví dụ cách super điều hướng qua mô hình kim cương.
Chủ đề
Python

Tìm hiểu thêm về Python

Khóa học

Nhập môn Python

4 giờ
7.1M
Nắm vững phân tích dữ liệu với Python chỉ trong 4 giờ. Khóa học online này giúp bạn làm quen với giao diện Python và các thư viện phổ biến.
Xem chi tiếtRight Arrow
Bắt Đầu Khóa Học
Xem thêmRight Arrow
Liên quan

blog

Claude Opus 4.6: Tính năng, Điểm chuẩn, Bài kiểm tra thực hành và hơn thế nữa

Mô hình mới nhất của Anthropic dẫn đầu ở mã hóa tác tử và lập luận phức tạp. Thêm vào đó, nó có cửa sổ ngữ cảnh 1M.
Matt Crabtree's photo

Matt Crabtree

10 phút

Xem ThêmXem Thêm