Chuyển đến nội dung chính

Giới thiệu về Metaclass trong Python

Trong hướng dẫn này, tìm hiểu metaclass là gì, cách triển khai chúng trong Python và cách tạo metaclass tùy chỉnh.
· 6 phút đọc

Khám phá cùng AI

Metaclass trong Python là lớp của một lớp, định nghĩa cách một lớp hoạt động. Bản thân một lớp là một thể hiện của metaclass. Một lớp trong Python định nghĩa cách các thể hiện của lớp đó sẽ hoạt động. Để hiểu rõ metaclass, bạn cần có kinh nghiệm làm việc với các lớp trong Python. Trước khi đi sâu vào metaclass, hãy cùng làm rõ một vài khái niệm.

Mọi thứ trong Python đều là Đối tượng

class TestClass():
    pass

my_test_class = TestClass()
print(my_test_class)
<__main__.TestClass object at 0x7f6fcc6bf908>

Lớp trong Python có thể được tạo động

type trong Python cho phép chúng ta kiểm tra kiểu của một đối tượng. Ta có thể tiếp tục kiểm tra kiểu của đối tượng đã tạo ở trên.

type(TestClass)
type
type(type)
type

Khoan, chuyện gì vừa xảy ra vậy? Ta mong đợi kiểu của đối tượng đã tạo ở trên là class, nhưng không phải. Hãy giữ suy nghĩ đó, chúng ta sẽ bàn tiếp sau. Ta cũng nhận thấy kiểu của chính type là type. Nó là một thể hiện của type. Một điều “kỳ diệu” khác mà type làm được là cho phép ta tạo lớp một cách động. Hãy xem cách thực hiện bên dưới. Lớp DataCamp dưới đây sẽ được tạo như sau bằng type:

class DataCamp():
    pass
DataCampClass = type('DataCamp', (), {})
print(DataCampClass)
print(DataCamp())
<class '__main__.DataCamp'>
<__main__.DataCamp object at 0x7f6fcc66e358>

Trong ví dụ trên, DataCamp là tên lớp, còn DataCampClass là biến giữ tham chiếu đến lớp. Khi dùng type ta có thể truyền các thuộc tính của lớp thông qua một dictionary như sau:

PythonClass = type('PythonClass', (), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass.start_date, PythonClass.instructor)
print(PythonClass)
August 2018 John Doe
<class '__main__.PythonClass'>

Nếu muốn PythonClass kế thừa từ lớp DataCamp, ta truyền nó vào đối số thứ hai khi định nghĩa lớp bằng type

PythonClass = type('PythonClass', (DataCamp,), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass)
<class '__main__.PythonClass'>

Giờ khi đã loại bỏ hai khái niệm đó, ta nhận ra rằng Python tạo các lớp bằng một metaclass. Chúng ta đã thấy mọi thứ trong Python là một đối tượng, và các đối tượng này được tạo bởi metaclass. Bất cứ khi nào ta gọi class để tạo lớp, sẽ có một metaclass đứng sau làm phép để tạo ra lớp đó. Chúng ta đã thấy type làm điều này ở trên. Nó tương tự như str tạo chuỗi và int tạo số nguyên. Trong Python, thuộc tính ___class__ cho phép ta kiểm tra kiểu của thể hiện hiện tại. Hãy tạo một chuỗi bên dưới và kiểm tra kiểu của nó.

article = 'metaclasses'
article.__class__
str

Ta cũng có thể kiểm tra kiểu bằng type(article).

type(article)
str

Khi kiểm tra kiểu của str, ta cũng thấy nó là type.

type(str)
type

Khi kiểm tra kiểu của float, int, list, tuple và dict, ta sẽ nhận được kết quả tương tự. Bởi vì tất cả các đối tượng này đều có kiểu là type.

print(type(list),type(float), type(dict), type(tuple))
<class 'type'> <class 'type'> <class 'type'> <class 'type'>

Chúng ta đã thấy type tạo ra các lớp. Do đó khi kiểm tra __class__ của __class__ nó sẽ trả về type.

article.__class__.__class__
type

Tạo Metaclass Tùy chỉnh

Trong Python, ta có thể tùy biến quy trình tạo lớp bằng cách truyền từ khóa metaclass trong định nghĩa lớp. Điều này cũng có thể thực hiện bằng cách kế thừa từ một lớp đã truyền sẵn từ khóa này.

class MyMeta(type):
    pass

class MyClass(metaclass=MyMeta):
    pass

class MySubclass(MyClass):
    pass

Ta có thể thấy bên dưới rằng kiểu của lớp MyMeta là type và kiểu của MyClass và MySubClass là MyMeta.

print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))
<class 'type'>
<class '__main__.MyMeta'>
<class '__main__.MyMeta'>

Khi định nghĩa một lớp mà không chỉ định metaclass, metaclass mặc định type sẽ được dùng. Nếu một metaclass được cung cấp và nó không phải là thể hiện của type(), thì nó sẽ được dùng trực tiếp làm metaclass.

__new__ và __init__

Metaclass cũng có thể được định nghĩa theo một trong hai cách dưới đây. Chúng ta sẽ giải thích sự khác nhau giữa chúng ngay sau đó.

class MetaOne(type):
    def __new__(cls, name, bases, dict):
        pass

class MetaTwo(type):
    def __init__(self, name, bases, dict):
        pass

__new__ được dùng khi muốn xác định dict hoặc tuple bases trước khi lớp được tạo. Giá trị trả về của __new__ thường là một thể hiện của cls. __new__ cho phép các lớp con của kiểu bất biến tùy biến việc tạo thể hiện. Nó có thể được ghi đè trong metaclass tùy chỉnh để tùy biến việc tạo lớp. __init__ thường được gọi sau khi đối tượng đã được tạo nhằm khởi tạo nó.

Phương thức __call__ của Metaclass

Theo tài liệu chính thức, ta cũng có thể ghi đè các phương thức lớp khác bằng cách định nghĩa phương thức __call__() tùy chỉnh trong metaclass để cho phép hành vi tùy biến khi lớp được gọi.

Phương thức __prepare__ của Metaclass

Theo tài liệu mô hình dữ liệu của Python

Sau khi metaclass thích hợp đã được xác định, không gian tên của lớp sẽ được chuẩn bị. Nếu metaclass có thuộc tính __prepare__, nó được gọi như namespace = metaclass.__prepare__(name, bases, **kwds) (trong đó các đối số từ khóa bổ sung, nếu có, đến từ định nghĩa lớp). Nếu metaclass không có thuộc tính __prepare__, không gian tên của lớp được khởi tạo như một ánh xạ có thứ tự rỗng. - docs.python.org

Thiết kế Singleton bằng Metaclass

Đây là một mẫu thiết kế giới hạn việc khởi tạo một lớp chỉ còn một đối tượng. Điều này có thể hữu ích, chẳng hạn khi thiết kế lớp kết nối đến cơ sở dữ liệu. Bạn có thể muốn chỉ có một thể hiện của lớp kết nối.

class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonMeta,cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class SingletonClass(metaclass=SingletonMeta):
    pass

Kết luận

Trong bài viết này, chúng ta đã tìm hiểu metaclass là gì và cách triển khai chúng trong lập trình Python. Metaclass có thể được áp dụng vào ghi log, đăng ký lớp tại thời điểm tạo và profiling, cùng nhiều mục đích khác. Chúng có vẻ là khái niệm khá trừu tượng, và bạn có thể tự hỏi liệu mình có cần dùng chúng hay không. Lập trình viên Python kỳ cựu Tim Peters trả lời câu hỏi đó một cách súc tích.

"Metaclass là phép thuật sâu hơn mức 99% người dùng cần bận tâm. Nếu bạn băn khoăn liệu mình có cần chúng không, thì là không (những người thực sự cần sẽ biết chắc họ cần, và không cần lời giải thích vì sao).” - realpython

Nếu bạn muốn tìm hiểu thêm về Python, hãy bắt đầu lộ trình kỹ năng Python Programming của DataCamp và xem Hướng dẫn Python Classes của chúng tôi.

Tài liệu tham khảo

Liên kết một

Liên kết hai

Chủ đề

Liên quan