Hiểu sâu về GIL (Global Interpreter Lock)
Trong môi trường triển khai thực tế của CPython (bộ thông dịch phổ biến nhất của Python), GIL đóng vai trò như một cơ chế khóa Mutex (đ loại trừ lẫn nhau) quan trọng. Cơ chế này đảm bảo rằng chỉ có một luồng (thread) duy nhất được phép thực thi bytecode Python tại một thời điểm nhất định, ngay cả khi chạy trên các hệ thống đa nhân (multi-core).
Về mặt lý thuyết, điều này hạn chế hiệu quả của đa luồng trong các tác vụ tính toán nặng (CPU-bound). Tuy nhiên, trong các tác vụ I/O-bound (như request mạng, đọc ghi file), luồng đang thực thi sẽ tự động giải phóng GIL trước khi chờ I/O hoàn tất, cho phép các luồng khác tiếp tục chạy. Do đó, đa luồng vẫn mang lại hiệu suất tốt hơn so với đơn luồng trong các ứng dụng crawler hoặc xử lý mạng. Để tận dụng tối đa tài nguyên CPU cho các tác vụ tính toán phức tạp, lập trình viên thường sử dụng đa tiến trình (multiprocessing) thay vì đa luồng.
Chiến lược sao chép đối tượng: Shallow Copy và Deep Copy
Việc quản lý bộ nhớ và sao chép dữ liệu trong Python đòi hỏi sự hiểu biết rõ ràng về sự khác biệt giữa sao chép nông (shallow copy) và sao chép sâu (deep copy).
Sao chép nông (Shallow Copy): Tạo ra một đối tượng mới và chèn các tham chiếu của đối tượng cũ vào nó. Nếu là đối tượng chứa các đối tượng con (nested objects), các đối tượng con này vẫn được chia sẻ bộ nhớ.
Sao chép sâu (Deep Copy): Tạo ra một đối tượng mới và sao chép đệ quy tất cả các đối tượng con được tìm thấy. Kết quả là hai đối tượng hoàn toàn độc lập về bộ nhớ.
Hàm copy.copy() thực hiện shallow copy, trong khi copy.deepcopy() thực hiện deep copy. Đặc biệt lưu ý, copy.copy() đối với các kiểu dữ liệu bất biến (như tuple, số nguyên) thường chỉ trả về tham chiếu đến đối tượng gốc thay vì tạo bản sao.
import copy
# Ví dụ với cấu trúc dữ liệu lồng nhau
original_config = {
"server": "localhost",
"settings": {"timeout": 30, "retries": 3}
}
# Thực hiện shallow copy
config_v1 = copy.copy(original_config)
# Thực hiện deep copy
config_v2 = copy.deepcopy(original_config)
# Thay đổi dữ liệu lồng nhau trong bản shallow copy
config_v1["settings"]["timeout"] = 60
print("Gốc sau khi thay đổi shallow copy:", original_config)
# Kết quả: 'timeout' trong original_config cũng bị thay đổi thành 60
print("Deep copy:", config_v2["settings"]["timeout"])
# Kết quả: Vẫn là 30, không bị ảnh hưởng
Quy tắc đóng gói và Name Mangling
Python sử dụng các quy tắc đặt tên để kiểm soát phạm vi truy cập của thuộc tính và phương thức, tạo ra cơ chế "riêng tư" (private) và "bảo vệ" (protected).
public_var: Biến công khai, có thể truy cập tự do từ bên ngoài._protected_var: Một dấu gạch dưới đầu. Đây là quy ước chỉ ra rằng biến/phương thức nên được coi là nội bộ (internal use). Khi sử dụngfrom module import *, các biến này sẽ không bị import.__private_var: Hai dấu gạch dưới đầu. Kích hoạt cơ chế "Name Mangling". Python sẽ thay đổi tên của biến thành_ClassName__private_varđể ngăn chặn việc ghi đè accidentally trong lớp con và khó truy cập trực tiếp từ bên ngoài.
class SecureAccount:
def __init__(self, owner, balance):
self.owner = owner
self._audit_log = [] # Biến protected
self.__secret_pin = "0000" # Biến private
def deposit(self, amount):
self._audit_log.append(f"Deposit {amount}")
self.__process_transaction(amount)
def __process_transaction(self, amount):
# Phương thức private
print(f"Processing secure transaction: {amount}")
acc = SecureAccount("Alice", 1000)
acc.deposit(500)
# Truy cập biến protected (không khuyến khích nhưng vẫn được)
print(acc._audit_log)
# Cố gắng truy cập biến private sẽ gây lỗi nếu không dùng name mangling
# print(acc.__secret_pin) # Lỗi AttributeError
# Cách truy cập đúng thông qua name mangling
print(acc._SecureAccount__secret_pin)
Quản lý Module và Import
Việc nạp module trong Python tuân theo thứ tự tìm kiếm trong danh sách sys.path. Python sẽ tìm kiếm tuần tự từ thư mục hiện tại, các thư mục chuẩn, đến các thư mục bên thứ ba.
Để thêm đường dẫn mới vào runtime, ta có thể thao tác trực tiếp trên sys.path:
import sys
# Thêm đường dẫn tùy chỉnh vào đầu danh sách để ưu tiên tìm kiếm
sys.path.insert(0, '/opt/custom_modules')
Một điểm chú ý trong phát triển đa module là vấn đề "circular dependency" hoặc trạng thái chia sẻ. Khi một module được import, mã trong cấp độ toàn cục của module đó sẽ chạy một lần duy nhất. Nếu một module khác import biến từ module này (sử dụng from module import variable), nó sẽ nhận được một tham chiếu đến giá trị tại thời điểm import. Nếu giá trị gốc thay đổi, module đã import sẽ không thấy sự thay đổi đó trừ khi nó truy cập qua tên module (ví dụ: module.variable).
Tính hướng đối tượng: Đóng gói, Kế thừa và Đa hình
Đóng gói (Encapsulation)
Mục tiêu là gom nhóm dữ liệu (thuộc tính) và các phương thức thao tác trên dữ liệu đó vào một đơn vị duy nhất (lớp). Điều này giúp ẩn đi chi tiết triển khai phức tạp, giảm sự phụ thuộc vào các biến toàn cục và tăng tính bảo mật, dễ bảo trì.
Kế thừa (Inheritance)
Kế thừa cho phép tái sử dụng mã nguồn. Một lớp con (subclass) có thể nhận toàn bộ đặc tính của lớp cha (superclass) và mở rộng thêm các tính năng mới hoặc sửa đổi hành vi của lớp cha. Điều này giúp xây dựng cấu trúc phân cấp mã nguồn rõ ràng.
Đa hình (Polymorphism)
Đa hình cho phép các đối tượng thuộc các lớp khác nhau được xử lý giống nhau thông qua một giao diện chung. Thường thấy nhất qua việc ghi đè phương thức (method overriding).
class NotificationSystem:
def __init__(self):
self.notifications = []
def send(self, notification):
notification.execute()
self.notifications.append(notification.name)
class BaseNotification:
def __init__(self, name):
self.name = name
def execute(self):
raise NotImplementedError("Subclasses must implement this method")
class EmailNotification(BaseNotification):
def execute(self):
print(f"Đang gửi Email từ: {self.name}...")
class SMSNotification(BaseNotification):
def execute(self):
print(f"Đang gửi SMS từ: {self.name}...")
# Sử dụng đa hình
system = NotificationSystem()
msg1 = EmailNotification("Server Alert")
msg2 = SMSNotification("User Login")
# Cả hai đối tượng gọi cùng phương thức execute nhưng hành vi khác nhau
system.send(msg1)
system.send(msg2)