Trong lập trình hướng đối tượng, các đối tượng được tạo ra từ lớp (class). Tuy nhiên ở Python, mọi thứ đều là đối tượng - kể cả chính các lớp. Lớp bản chất là một đối tượng đặc biệt có khả năng sinh ra các thể hiện (instance). Vậy ai tạo ra chính các lớp này? Câu trả lời chính là metaclass - "lớp của lớp".
Trong Python, metaclass mặc định là type. Thay vì dùng cú pháp class thông thường, ta có thể tạo lớp động thông qua hàm type():
SanPham = type('SanPham', (object,), {})
Cơ chế này tương đương với khai báo:
class SanPham(object):
pass
Hàm type nhận 3 tham số:
- Tên lớp cần tạo
- Bộ cha kế thừa (tuple)
- Từ điển thuộc tính và phương thức
Ví dụ minh họa việc tạo lớp với phương thức đầy đủ:
class Nguoi:
ten_goc = 'con_nguoi'
def khoi_tao(self, gia_tri):
self.gia_tri = gia_tri
def cong(self, so):
return self.gia_tri + so
NguoiCon = type('NguoiCon', (Nguoi,), {
'ten_lop': 'be_minh',
'__init__': khoi_tao,
'tinh_tong': cong
})
be = NguoiCon(5)
print(be.ten_goc) # Kết quả: con_nguoi
print(be.tinh_tong(3)) # Kết quả: 8
Để tự tạo metaclass, ta kế thừa từ type và ghi đè phương thức __new__. Ví dụ sau chuyển tất cả thuộc tính lớp thành chữ hoa:
class MetaClassChuyenDoi(type):
def __new__(cls, ten_lop, tap_cha, tu_dien_thuoc_tinh):
thuoc_tinh_moi = {}
for ten, gia_tri in tu_dien_thuoc_tinh.items():
if not ten.startswith('__'):
thuoc_tinh_moi[ten.upper()] = gia_tri
else:
thuoc_tinh_moi[ten] = gia_tri
return type(ten_lop, tap_cha, thu_dien_thuoc_tinh_moi)
class DoVat(metaclass=MetaClassChuyenDoi):
ten = 'may_tinh'
print(hasattr(DoVat, 'TEN')) # True
print(hasattr(DoVat, 'ten')) # False
Áp dụng metaclass để xây dựng ORM (Object-Relational Mapping) - cơ chế ánh xạ đối tượng sang cơ sở dữ liệu. Mục tiêu: Khi gọi phương thức luu() trên thể hiện lớp, tự sinh câu lệnh SQL tương ứng.
Trước tiên, ta xây dựng metaclass chuyên dụng:
class ORM_Metaclass(type):
def __new__(cls, ten_lop, lop_cha, thuoc_tinh):
bang_thong_tin = {}
for ten, gia_tri in thuoc_tinh.items():
if isinstance(gia_tri, tuple) and len(gia_tri) == 2:
bang_thong_tin[ten] = gia_tri[0]
for k in bang_thong_tin:
del thuoc_tinh[k]
thuoc_tinh['_truong'] = bang_thong_tin
thuoc_tinh['_ten_bang'] = ten_lop
return super().__new__(cls, ten_lop, lop_cha, thuoc_tinh)
Sử dụng metaclass để định nghĩa lớp thao tác cơ sở dữ liệu:
class SanPham(metaclass=ORM_Metaclass):
id_san_pham = ('ma_sp', 'INT UNSIGNED')
ten = ('ten_sp', 'VARCHAR(50)')
gia = ('gia_ban', 'DECIMAL(10,2)')
def __init__(self, **kwargs):
for k, v in kwargs.items():
setattr(self, k, v)
def luu(self):
danh_sach_truong = []
gia_tri = []
for ten_truong, ten_cot in self._truong.items():
danh_sach_truong.append(ten_cot)
gia_tri_hien_tai = getattr(self, ten_truong, None)
if isinstance(gia_tri_hien_tai, str):
gia_tri_hien_tai = f""{gia_tri_hien_tai}""
gia_tri.append(str(gia_tri_hien_tai))
cau_truy_van = (
f"INSERT INTO {self._ten_bang} "
f"({','.join(danh_sach_truong)}) "
f"VALUES ({','.join(gia_tri)})"
)
print(cau_truy_van)
sp_moi = SanPham(id_san_pham=101, ten='Laptop', gia=25000000)
sp_moi.luu()
# Kết quả: INSERT INTO SanPham (ma_sp,ten_sp,gia_ban) VALUES (101,"Laptop",25000000)