Giao diện
Classes & Objects OOP
Python OOP = Đơn giản hơn là Phức tạp
Cơ bản về Class
Cấu trúc của một Class
python
class User:
"""Một class user đơn giản."""
# Class attribute (chia sẻ bởi tất cả instances)
platform: str = "PENALGO"
def __init__(self, ten: str, email: str) -> None:
"""Constructor - được gọi khi tạo instance."""
# Instance attributes (riêng biệt cho mỗi instance)
self.ten = ten
self.email = email
self.hoat_dong = True
def chao(self) -> str:
"""Instance method."""
return f"Xin chào, tôi là {self.ten}"
# Tạo instances
user1 = User("HPN", "HPN@penalgo.io")
user2 = User("Tom", "tom@penalgo.io")
print(user1.chao()) # "Xin chào, tôi là HPN"
print(User.platform) # "PENALGO" (class attribute)Phương thức __init__
__init__ là constructor của Python - được gọi tự động khi tạo object.
python
class SanPham:
def __init__(
self,
ten: str,
gia: float,
so_luong: int = 0 # Giá trị mặc định
) -> None:
self.ten = ten
self.gia = gia
self.so_luong = so_luong
# Thuộc tính tính toán
self.tong_gia_tri = gia * so_luong
# Sử dụng
laptop = SanPham("MacBook", 2000, so_luong=5)
print(laptop.tong_gia_tri) # 10000Các Pattern Phổ biến
python
class Config:
def __init__(self, **kwargs) -> None:
"""Nhận bất kỳ keyword arguments nào."""
self.debug = kwargs.get("debug", False)
self.log_level = kwargs.get("log_level", "INFO")
# Lưu tất cả config thêm
self._extra = kwargs
config = Config(debug=True, custom_setting="value")Tại sao self?
Python dùng explicit self thay vì implicit this như Java/C#.
python
class Counter:
def __init__(self) -> None:
self.dem = 0 # self = instance hiện tại
def tang(self) -> None:
self.dem += 1 # Phải dùng self để truy cập instance attribute
def lay_gia_tri(self) -> int:
return self.dem
# Khi gọi method:
counter = Counter()
counter.tang() # Python tự động truyền `counter` như `self`
# Thực chất là:
Counter.tang(counter) # Tương đương!Tại sao Explicit tốt hơn
python
# ✅ TỐT: Rõ ràng biến đến từ đâu
class User:
def cap_nhat_ten(self, ten_moi: str) -> None:
self.ten = ten_moi # Rõ ràng là instance attribute
bien_tam = "temp" # Rõ ràng là biến local
# ❌ Kiểu Java: `ten` đến từ đâu?
# public void updateName(String newName) {
# name = newName; // Instance? Local? Parameter?
# }💡 TRIẾT LÝ PYTHON
"Explicit is better than implicit." - Python yêu cầu self để code rõ ràng hơn.
Class Attribute vs Instance Attribute
python
class NhanVien:
# Class attribute - chia sẻ bởi TẤT CẢ instances
cong_ty = "PENALGO"
so_nhan_vien = 0
def __init__(self, ten: str) -> None:
# Instance attribute - riêng biệt cho MỖI instance
self.ten = ten
NhanVien.so_nhan_vien += 1
# Class attributes
print(NhanVien.cong_ty) # "PENALGO"
# Instance attributes
nv1 = NhanVien("Alice")
nv2 = NhanVien("Bob")
print(nv1.ten) # "Alice"
print(nv2.ten) # "Bob"
print(NhanVien.so_nhan_vien) # 2Bẫy Mutable Class Attribute ⚠️
python
# ❌ NGUY HIỂM: Mutable class attribute
class TeamXau:
thanh_vien = [] # CHIA SẺ bởi tất cả instances!
def them_thanh_vien(self, ten: str) -> None:
self.thanh_vien.append(ten)
team1 = TeamXau()
team2 = TeamXau()
team1.them_thanh_vien("Alice")
print(team2.thanh_vien) # ["Alice"] - Sao lại thế?!
# ✅ SỬA: Khởi tạo trong __init__
class TeamTot:
def __init__(self) -> None:
self.thanh_vien = [] # Riêng biệt cho mỗi instanceOOP Hiện đại: @dataclass
Python 3.7+ có @dataclass - tự động generate code boilerplate.
Trước: Địa ngục Boilerplate
python
# ❌ CÁCH CŨ: Quá nhiều code...
class User:
def __init__(self, ten: str, email: str, tuoi: int) -> None:
self.ten = ten
self.email = email
self.tuoi = tuoi
def __repr__(self) -> str:
return f"User(ten={self.ten!r}, email={self.email!r}, tuoi={self.tuoi!r})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, User):
return NotImplemented
return (self.ten, self.email, self.tuoi) == (other.ten, other.email, other.tuoi)Sau: Phép màu @dataclass
python
from dataclasses import dataclass
# ✅ HIỆN ĐẠI: Một decorator, có tất cả
@dataclass
class User:
ten: str
email: str
tuoi: int
# Tự động có:
# - __init__
# - __repr__
# - __eq__
# - Và nhiều hơn!
user = User("HPN", "HPN@x.com", 28)
print(user) # User(ten='HPN', email='HPN@x.com', tuoi=28)Tính năng của Dataclass
python
from dataclasses import dataclass, field
from typing import ClassVar
@dataclass
class SanPham:
# Trường bắt buộc (không có default)
ten: str
gia: float
# Trường tùy chọn (có default)
so_luong: int = 0
tags: list[str] = field(default_factory=list) # Mutable default
# Trường tính toán (không trong __init__)
tong: float = field(init=False)
# Class variable (không phải instance attribute)
tien_te: ClassVar[str] = "VND"
def __post_init__(self) -> None:
"""Được gọi sau __init__."""
self.tong = self.gia * self.so_luong
san_pham = SanPham("Laptop", 1000, so_luong=5)
print(san_pham.tong) # 5000Dataclass Bất biến
python
from dataclasses import dataclass
@dataclass(frozen=True) # Bất biến!
class Diem:
x: float
y: float
diem = Diem(10, 20)
diem.x = 30 # ❌ FrozenInstanceError!Dataclass với Slots (Python 3.10+)
python
@dataclass(slots=True) # Tiết kiệm bộ nhớ!
class User:
ten: str
email: str
# Dùng __slots__ bên trong - ít bộ nhớ, truy cập nhanh hơnKhi nào Dùng gì?
| Trường hợp | Giải pháp |
|---|---|
| Container dữ liệu đơn giản | @dataclass |
| Dữ liệu bất biến | @dataclass(frozen=True) |
| Logic khởi tạo phức tạp | Class thường với __init__ |
| Cần kế thừa | Class thường (kế thừa dataclass phức tạp) |
| Objects cấu hình | @dataclass với defaults |
⚠️ QUY TẮC CỦA HPN
Nếu class của bạn chủ yếu là lưu trữ dữ liệu → Dùng @dataclass.
Nếu class có hành vi phức tạp → Dùng class thường.
Bảng Tóm tắt
python
# === CLASS CƠ BẢN ===
class MyClass:
class_attr = "chia_se" # Class attribute
def __init__(self, gia_tri):
self.gia_tri = gia_tri # Instance attribute
def phuong_thuc(self):
return self.gia_tri
# === DATACLASS (Python 3.7+) ===
from dataclasses import dataclass, field
@dataclass
class Data:
bat_buoc: str # Bắt buộc
tuy_chon: int = 0 # Có default
mutable: list = field(default_factory=list) # Mutable default
tinh_toan: str = field(init=False) # Không trong __init__
def __post_init__(self):
self.tinh_toan = self.bat_buoc.upper()
# === FROZEN DATACLASS ===
@dataclass(frozen=True)
class BatBien:
x: int
y: int
# === SLOTS DATACLASS (3.10+) ===
@dataclass(slots=True)
class HieuQua:
ten: str