Học bằng cách đọc theo thứ tự

Kế thừa class — Tái sử dụng tính năng từ class có sẵn

Học kế thừa class trong Python. Kế thừa thuộc tính và phương thức bằng class Child(Parent):, override phương thức, và gọi phiên bản của lớp cha với super() — kèm sơ đồ.

Lần trước ta dùng các phương thức đặc biệt như __add____str__ để dạy các thao tác có sẵn cho class của mình. Lần này ta bao quát một trong những ý tưởng cốt lõi của OOP: kế thừa class.

Tại sao dùng kế thừa?

Khi xây dựng app, bạn thường gặp những class gần như giống nhau nhưng khác ở vài chỗ. Hãy tưởng tượng Customer, Employee, và Admin — tất cả đều giữ nameemail rồi trả về một greeting(), nhưng mỗi class có thêm vài thuộc tính hoặc phương thức riêng.

Bạn có thể viết cùng name / email / greeting() ở cả ba class — nhưng code sẽ bị trùng lặp và mỗi thay đổi phải chạm vào ba chỗ. Cách gọn gàng là đặt phần dùng chung vào một lớp cha và để mỗi lớp con «kế thừa từ lớp cha và chỉ viết phần khác biệt». Đó là kế thừa (inheritance).

Kéo code dùng chung lên lớp cha
Person(lớp cha)name / agegreeting()Employee(lớp con)kế thừa
Thuộc tính và phương thức dùng chung nằm ở lớp cha (Person). Chỉ phần khác biệt mới vào lớp con (Employee). Không cần khai báo lại name hay greeting mỗi lần.

Kế thừa từ lớp cha — class Child(Parent):

Cú pháp đơn giản: khi định nghĩa subclass, đặt tên lớp cha trong dấu ngoặc tròn sau tên class. Subclass tự động lấy hết thuộc tính và phương thức của lớp cha.

Dưới đây, Person là lớp cha (có name, age, và greeting) và Employee là lớp con. Employee không định nghĩa một phương thức nào của riêng nó, nhưng gọi greeting() trên nó vẫn chạy nhờ kế thừa.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age  = age

    def greeting(self):
        print(f"Xin chào, {self.name}")

    def say_age(self):
        print(f"{self.age} tuổi")


class Employee(Person):       # kế thừa Person
    pass                       # phần thân có thể để trống


e = Employee("Minh", 45)
e.greeting()                  # Xin chào, Minh   <- phương thức của lớp cha
e.say_age()                   # 45 tuổi
Lớp con nhận mọi thứ lớp cha có
Person (lớp cha)
  • Thuộc tính: name / age
  • Phương thức: greeting() / say_age()
  • __init__(self, name, age)
Employee (lớp con)
  • Phần thân để trống (chỉ pass)
  • Kế thừa thuộc tính và phương thức của lớp cha
  • Employee("Minh", 45) gọi __init__ của lớp cha
Ngay cả khi lớp con để trống, mọi thứ lớp cha có đều dùng được. Tái sử dụng hành vi mà không phải viết lại chính là toàn bộ ý nghĩa của kế thừa.

Xây dựng một lớp cha Person và một Employee chỉ kế thừa từ nó.

① Định nghĩa class Person: với __init__(self, name, age) để gán self.nameself.age.

② Trong Person, định nghĩa greeting(self) để chạy print(f"Xin chào, {self.name}").

③ Viết class Employee(Person): với phần thân chỉ là pass.

④ Tạo e = Employee("Minh", 45) và gọi e.greeting().

(Khi chạy đúng, phần giải thích sẽ hiện ra.)

Python Editor

Chạy code để xem đầu ra

Override phương thức — Thay thế bằng cùng tên

Chỉ kế thừa thì bạn nhận được hành vi của lớp cha y nguyên. Nhưng nếu bạn viết một phương thức cùng tên trong lớp con, bản của lớp con sẽ được ưu tiên. Đó là override (ghi đè).

Ví dụ, để cho Employee một lời chào khác như «Xin chào, nhân viên Minh», định nghĩa lại greeting trong lớp con. Gọi nó trên một instance Person cho lời chào của lớp cha; gọi trên một instance Employee cho lời chào của lớp con. Hành vi chuyển đổi dựa trên class mà instance đang gọi thuộc về.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age  = age

    def greeting(self):
        print(f"Xin chào, {self.name}")


class Employee(Person):
    def greeting(self):                # cùng tên = override
        print(f"Xin chào, nhân viên {self.name}")


p = Person("Linh", 30)
e = Employee("Minh", 45)
p.greeting()    # Xin chào, Linh
e.greeting()    # Xin chào, nhân viên Minh    <- bản của lớp con thắng
Thứ tự tìm phương thức
e.greeting()greeting trênEmployee?→ chạygreeting trênPerson?chạy nếu có,không thì lỗi① tìmnếu Không
Python tìm phương thức ở lớp con trước. Nếu không có, leo lên lớp cha. Cùng tên → lớp con thắng — đó là override.

Thêm một override greeting vào code trước.

① Tạo cùng class Person như trước (__init__greeting).

② Định nghĩa class Employee(Person): và bên trong định nghĩa lại greeting(self) để chạy print(f"Xin chào, nhân viên {self.name}").

③ Tạo Person("Linh", 30)Employee("Minh", 45), sau đó gọi greeting() cho mỗi cái và so sánh.

Python Editor

Chạy code để xem đầu ra

super() — Gọi phương thức của lớp cha

Đôi khi bạn muốn override một phương thức nhưng vẫn dựa trên hành vi của lớp cha. Đó là lúc dùng super(). Viết super().method() gọi phương thức cùng tên trên lớp cha của class hiện tại.

Ví dụ, giả sử bạn muốn «greeting của lớp cha (in Xin chào, Minh) theo sau là một dòng phụ ở lớp con». Trong greeting của lớp con, gọi super().greeting() trước rồi thêm dòng riêng của bạn.

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age  = age

    def greeting(self):
        print(f"Xin chào, {self.name}")


class Employee(Person):
    def greeting(self):
        super().greeting()                            # ① chạy greeting của lớp cha trước
        print(f"Tôi là nhân viên {self.name}")        # ② rồi đến dòng riêng của lớp con


Employee("Minh", 45).greeting()
# Xin chào, Minh
# Tôi là nhân viên Minh
super().greeting() tái sử dụng logic của lớp cha
Employee.greeting()thân greetinglớp consuper().greeting()chạy greetingcủa lớp chaphần còn lạithân lớp congọichạy
Trong greeting của lớp con, gọi super().greeting() chạy greeting của lớp cha trực tiếp. self được truyền tự động — bạn không viết.

Override __init__ để thêm thuộc tính

Nơi super() tỏa sáng nhất là khi bạn override __init__. Hãy tưởng tượng lớp con cần thuộc tính của lớp cha cộng thêm vài thuộc tính riêng — ví dụ, Employee cũng nên giữ phone_number.

Trong trường hợp đó bạn định nghĩa lại __init__ ở lớp con, nhưng việc khởi tạo của lớp cha (self.name = name v.v.) được xử lý gọn trong một dòng bằng super().__init__(name, age), và bạn thêm các thuộc tính phụ sau đó. Như vậy bạn không phải lặp lại các phép gán name / age.

__init__ lớp con xây cả thuộc tính lớp cha lẫn lớp con
Employee("Minh", 45, "555-1111")__init__ lớp conchạysuper().__init__(name, age)lớp cha gánname / ageself.phone_number= phone_numberthuộc tính cha + consẵn sànggọi
Gọi Employee(...) chạy __init__ của lớp con → super().__init__(name, age) để lớp cha gán name / age → rồi lớp con thêm phone_number. Trạng thái cuối: thuộc tính lớp cha + lớp con đầy đủ.
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age  = age


class Employee(Person):
    def __init__(self, name, age, phone_number):
        super().__init__(name, age)               # ① lớp cha set name / age
        self.phone_number = phone_number           # ② thêm thuộc tính riêng của lớp con

    def show(self):
        print(f"{self.name} / {self.age} / {self.phone_number}")


Employee("Minh", 45, "555-1111").show()
# Minh / 45 / 555-1111

Quên super().__init__(...) thì thuộc tính biến mất

Nếu bạn override __init__ nhưng quên gọi super().__init__(...), các phép gán của lớp cha như self.name = name không bao giờ chạy. Lần tới bạn đọc self.name, bạn sẽ nhận AttributeError. Mỗi khi override __init__ ở lớp con, gọi super().__init__(...) ngay đầu tiên — biến nó thành thói quen.

Thêm phone_number cho Employee bằng cách override __init__.

① Định nghĩa class Person: với __init__(self, name, age) để gán self.nameself.age.

② Định nghĩa class Employee(Person): với __init__(self, name, age, phone_number). Bên trong, gọi super().__init__(name, age) trước, sau đó self.phone_number = phone_number.

③ Trong Employee, định nghĩa show(self) để chạy print(f"{self.name} / {self.age} / {self.phone_number}").

④ Tạo Employee("Minh", 45, "555-1111") và gọi show().

Python Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Cách nào sau đây đúng để cho Dog kế thừa từ Animal?

Câu 2Mô tả nào chính xác nhất về override phương thức?

Câu 3Nếu bạn quên gọi super().__init__(...) trong __init__ của lớp con, điều gì xảy ra?