Khám phá Cơ chế Mixins và Metaclass trong Python

1. Mô hình Mixins

Mixins là một kỹ thuật trong lập trình hướng đối tượng giúp tái sử dụng mã nguồn linh hoạt. Thay vì xây dựng cây phân cấp sâu, chúng ta có thể đưa các chức năng cụ thể vào các lớp độc lập và kết hợp chúng khi cần thiết.

Ví dụ, xét hệ thống giao thông. Các phương tiện như máy bay dân dụng, trực thăng và ô tô đều là phương tiện di chuyển. Tuy nhiên, chỉ có máy bay và trực thăng mới bay được. Do đó, chức năng bay nên được tách riêng thành một lớp phụ trợ.


class Transport:
    """Lớp cơ sở cho các phương tiện"""
    def move(self):
        print("Đang di chuyển trên mặt đất")

class FlightAbility:
    """Mixin cung cấp khả năng bay"""
    def fly(self):
        print("Đang bay trên bầu trời")

# Máy bay dân dụng kế thừa cả khả năng bay và phương tiện
class JetPlane(FlightAbility, Transport):
    pass

# Trực thăng cũng có khả năng bay
class Chopper(FlightAbility, Transport):
    pass

# Ô tô chỉ di chuyển trên mặt đất
class Sedan(Transport):
    pass

Một số quy tắc khi sử dụng Mixins:

  • Lớp chính chứa chức năng cốt lõi của đối tượng.
  • Lớp phụ (Mixin) chứa chức năng bổ trợ, không đứng độc lập.
  • Tên lớp Mixin thường kết thúc bằng suffix như Mixin, Ability, Capable để dễ nhận biết.
  • Thứ tự kế thừa ảnh hưởng đến việc tìm kiếm phương thức (MRO), cần sắp xếp cẩn thận.

2. Metaclass (Siêu lớp)

Metaclass được hiểu là lớp dùng để tạo ra các lớp khác. Nếu ví lớp là bản thiết kế cho đối tượng, thì metaclass là bản thiết kế cho lớp.

2.1. Kiểm tra metaclass mặc định

Mọi lớp trong Python đều được tạo ra bởi một metaclass, mặc định là type.


class Transport:
    pass

class Sedan(Transport):
    pass

xe = Transport()
print(type(xe))      # <class '__main__.Transport'>
print(type(Transport)) # <class 'type'>

Kết quả cho thấy instance xe thuộc lớp Transport, còn bản thân lớp Transport là một đối tượng của type.

2.2. Hai cách khởi tạo lớp

Cách thông thường sử dụng từ khóa class:


class TenLop:
    pass

Cách thứ hai sử dụng trực tiếp hàm dựng type, vì mọi lớp đều được sinh ra từ đây:


# type(tên_lớp, tuple_cha, dict_thuộc_tính)
DynamicClass = type("DynamicClass", (), {"attr": 1})

2.3. Mối quan hệ giữa type và object

Trong Python, type là metaclass mặc định, chịu trách nhiệm tạo ra các lớp. Trong khi đó, object là lớp cơ sở mà mọi lớp đều kế thừa ngầm định. object cung cấp các phương thức nền tảng như __str__, __eq__. Tóm lại, type tạo ra lớp, còn object là gốc của cây kế thừa đối tượng.

2.4. Tùy chỉnh Metaclass

Chúng ta không thể kế thừa trực tiếp từ một lớp bình thường để làm metaclass, mà phải kế thừa từ type. Metaclass được chỉ định qua tham số metaclass khi định nghĩa lớp.

Ví dụ 1: Validate tên lớp

Yêu cầu tên lớp phải bắt đầu bằng chữ in hoa khi định nghĩa.


class NameValidator(type):
    def __init__(cls, name, bases, attrs):
        if not name[0].isupper():
            raise ValueError("Tên lớp phải bắt đầu bằng chữ in hoa")
        super().__init__(name, bases, attrs)

class ValidClass(metaclass=NameValidator):
    pass

# class invalidClass(metaclass=NameValidator): # Sẽ gây lỗi
#     pass

Ví dụ 2: Kiểm soát quá trình khởi tạo

Metaclass có phương thức __call__, được gọi khi lớp được triệu gọi để tạo instance. Ta có thể chèn logic kiểm tra tại đây trước khi đối tượng được sinh ra.


class CallController(type):
    def __call__(cls, *args, **kwargs):
        print("Metaclass __call__ đang chạy")
        # Bắt buộc chỉ dùng keyword arguments
        if args:
            raise TypeError("Không chấp nhận positional arguments")
        return super().__call__(*args, **kwargs)

class User(metaclass=CallController):
    def __init__(self, username):
        self.username = username
        print("Instance __init__ đang chạy")

# Tạo đối tượng
user = User(username="admin")
# User("admin") sẽ gây lỗi do vi phạm quy tắc trong __call__

Khi thực hiện User(username="admin"), phương thức __call__ của metaclass chạy trước, kiểm tra tham số, sau đó mới đến __init__ của lớp. Nếu truyền tham số vị trí, ngoại lệ sẽ được ném ra ngay từ bước đầu tiên mà không cần khởi tạo đối tượng.

Thẻ: python MetaClass Mixins oop dynamic-class-creation

Đăng vào ngày 19 tháng 10 lúc 11:49