Lập trình hướng đối tượng giúp bạn gom dữ liệu và các thao tác trên dữ liệu đó vào cùng một chỗ. Với chương trình lớn, cách tổ chức này giúp code dễ hiểu và dễ mở rộng hơn nhiều.
Class đầu tiên
class NhanVien:
def __init__(self, ho_ten, luong):
self.ho_ten = ho_ten
self.luong = luong
def tang_luong(self, phan_tram):
self.luong *= (1 + phan_tram / 100)
return self.luong
def __str__(self):
return f"{self.ho_ten} - {self.luong:,.0f}đ"
nv = NhanVien("Nguyễn Văn A", 15_000_000)
nv.tang_luong(10)
print(nv) # Nguyễn Văn A - 16,500,000đ
__init__ chạy khi tạo đối tượng mới. self là chính đối tượng đó — Python truyền nó tự động, bạn chỉ cần khai báo.
Vì sao cần __str__ và __repr__
class Diem:
def __init__(self, x, y):
self.x, self.y = x, y
d = Diem(3, 4)
print(d) # <__main__.Diem object at 0x7f...> - vô dụng
Thêm hai phương thức này:
class Diem:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Diem(x={self.x}, y={self.y})" # cho lập trình viên
def __str__(self):
return f"({self.x}, {self.y})" # cho người dùng cuối
__repr__ là thứ hiện ra khi bạn gõ tên biến trong trình thông dịch, hoặc khi in một danh sách chứa đối tượng. Nếu chỉ viết một trong hai, hãy viết __repr__ — Python sẽ dùng nó thay cho __str__ khi thiếu.
Thuộc tính của class và của đối tượng
class TaiKhoan:
lai_suat = 0.05 # dùng chung cho mọi đối tượng
so_tai_khoan = 0
def __init__(self, chu_so_huu):
self.chu_so_huu = chu_so_huu # riêng từng đối tượng
TaiKhoan.so_tai_khoan += 1
a = TaiKhoan("An")
b = TaiKhoan("Bình")
print(TaiKhoan.so_tai_khoan) # 2
Cái bẫy dùng list làm thuộc tính class
class Gio:
mat_hang = [] # SAI - dùng chung cho tất cả
g1, g2 = Gio(), Gio()
g1.mat_hang.append("sách")
print(g2.mat_hang) # ['sách'] - giỏ của người khác cũng có!
Danh sách này được tạo một lần lúc định nghĩa class, nên mọi đối tượng dùng chung. Cách đúng là khởi tạo trong __init__:
class Gio:
def __init__(self):
self.mat_hang = [] # mỗi đối tượng một danh sách riêng
property: kiểm soát việc đọc ghi
class HinhTron:
def __init__(self, ban_kinh):
self._ban_kinh = ban_kinh
@property
def ban_kinh(self):
return self._ban_kinh
@ban_kinh.setter
def ban_kinh(self, gia_tri):
if gia_tri <= 0:
raise ValueError("Bán kính phải lớn hơn 0")
self._ban_kinh = gia_tri
@property
def dien_tich(self):
return 3.14159 * self._ban_kinh ** 2
h = HinhTron(5)
print(h.dien_tich) # 78.54 - gọi như thuộc tính
h.ban_kinh = -1 # ValueError
Python không có thuộc tính riêng tư thật sự. Dấu gạch dưới đầu tên là quy ước: “đây là chuyện nội bộ, đừng động vào từ bên ngoài”.
Phương thức của class và phương thức tĩnh
class NgayThang:
def __init__(self, ngay, thang, nam):
self.ngay, self.thang, self.nam = ngay, thang, nam
@classmethod
def tu_chuoi(cls, s):
ngay, thang, nam = map(int, s.split("/"))
return cls(ngay, thang, nam) # cách tạo đối tượng thứ hai
@staticmethod
def la_nam_nhuan(nam):
return nam % 4 == 0 and (nam % 100 != 0 or nam % 400 == 0)
d = NgayThang.tu_chuoi("15/03/2026")
print(NgayThang.la_nam_nhuan(2026)) # False
classmethod thường dùng làm “hàm khởi tạo thay thế” — tạo đối tượng từ định dạng dữ liệu khác. staticmethod là hàm bình thường nhưng đặt trong class vì liên quan về mặt logic.
dataclass: bớt code lặp
Với những class chỉ để chứa dữ liệu, Python có sẵn công cụ:
from dataclasses import dataclass, field
@dataclass
class SanPham:
ten: str
gia: float
so_luong: int = 0
the: list[str] = field(default_factory=list)
@property
def thanh_tien(self):
return self.gia * self.so_luong
sp = SanPham("Bàn phím", 450_000, 2)
print(sp) # SanPham(ten='Bàn phím', gia=450000, ...)
print(sp.thanh_tien) # 900000
Bạn được __init__, __repr__ và __eq__ miễn phí. Chú ý field(default_factory=list) — đây chính là cách tránh cái bẫy dùng chung danh sách nói ở trên.
Khi nào không cần class
Không phải bài toán nào cũng cần hướng đối tượng. Nếu class của bạn chỉ có __init__ và một phương thức duy nhất, nhiều khả năng một hàm là đủ:
# Thừa
class BoTinhThue:
def __init__(self, thue_suat):
self.thue_suat = thue_suat
def tinh(self, so_tien):
return so_tien * self.thue_suat
# Đủ rồi
def tinh_thue(so_tien, thue_suat=0.1):
return so_tien * thue_suat
Class phát huy giá trị khi bạn cần giữ trạng thái qua nhiều lần gọi, hoặc khi có nhiều biến thể cùng chia sẻ một giao diện chung.