
Python Tutorial — Từ Cơ Bản Đến Nâng Cao
Tài liệu học Python toàn diện bằng tiếng Việt. Mỗi phần gồm: lý thuyết/định nghĩa, giải thích, trường hợp sử dụng, ví dụ code và giải thích code.
Mục Lục
Phần I — Cơ Bản
- Giới thiệu về Python
- Cài đặt & Chạy chương trình
- Biến và Kiểu dữ liệu
- Toán tử (Operators)
- Chuỗi (Strings)
- Nhập / Xuất dữ liệu
- Cấu trúc điều kiện
- Vòng lặp
Phần II — Cấu Trúc Dữ Liệu
Phần III — Hàm & Module
- Hàm (Functions)
- Tham số nâng cao (*args, **kwargs)
- Lambda, map, filter, reduce
- Module & Package
- Xử lý ngoại lệ (Exceptions)
- Làm việc với File
Phần IV — Lập Trình Hướng Đối Tượng (OOP)
- Class & Object
- Kế thừa (Inheritance)
- Đa hình & Đóng gói
- Magic Methods (Dunder)
- Property, Classmethod, Staticmethod
Phần V — Nâng Cao
- Iterator & Generator
- Decorator
- Context Manager (with)
- Type Hints
- Lập trình bất đồng bộ (async/await)
- Đa luồng & Đa tiến trình
- Thư viện chuẩn hữu ích
- Best Practices & Mẹo
Phần I — Cơ Bản
1. Giới thiệu về Python
Định nghĩa: Python là ngôn ngữ lập trình bậc cao, thông dịch (interpreted), đa mục đích, do Guido van Rossum tạo ra năm 1991. Python nổi tiếng với cú pháp đơn giản, dễ đọc, gần với ngôn ngữ tự nhiên.
Đặc điểm chính:
- Thông dịch: Code chạy trực tiếp, không cần biên dịch ra file thực thi.
- Định kiểu động (dynamically typed): Không cần khai báo kiểu dữ liệu trước.
- Đa mô hình: Hỗ trợ lập trình hướng thủ tục, hướng đối tượng và hàm.
- Thụt lề có ý nghĩa: Dùng khoảng trắng (indentation) thay vì dấu
{}để xác định khối lệnh.
Trường hợp sử dụng: Web (Django, Flask, FastAPI), khoa học dữ liệu & AI (NumPy, Pandas, PyTorch), tự động hóa scripting, DevOps, ERP (như Odoo), crawling, IoT.
# Chương trình Python đầu tiên
print("Xin chào, Python!")
Giải thích: print() là hàm built-in dùng để xuất dữ liệu ra màn hình. Chuỗi được đặt trong dấu nháy " " hoặc ' '.
2. Cài đặt & Chạy chương trình
Lý thuyết: Python cần một trình thông dịch (interpreter) để chạy. Tải tại python.org.
Cách chạy:
# Kiểm tra phiên bản
python --version
# Chạy file .py
python hello.py
# Chạy trực tiếp lệnh
python -c "print('hello')"
# Mở chế độ tương tác (REPL)
python
Môi trường ảo (Virtual Environment) — RẤT QUAN TRỌNG:
Định nghĩa: Môi trường ảo là một thư mục độc lập chứa bản Python riêng và các thư viện riêng, giúp các dự án không xung đột thư viện với nhau.
# Tạo môi trường ảo tên venv
python -m venv venv
# Kích hoạt (Windows PowerShell)
venv\Scripts\Activate.ps1
# Kích hoạt (Linux/Mac)
source venv/bin/activate
# Cài thư viện
pip install requests
# Lưu danh sách thư viện
pip freeze > requirements.txt
# Cài lại từ file
pip install -r requirements.txt
Giải thích: Luôn tạo venv cho mỗi dự án để tránh việc thư viện của dự án A đè lên dự án B.
3. Biến và Kiểu dữ liệu
Định nghĩa: Biến là tên gọi tham chiếu đến một vùng nhớ chứa giá trị. Trong Python, biến không cần khai báo kiểu — kiểu được suy ra từ giá trị gán.
ten = "An" # str (chuỗi)
tuoi = 25 # int (số nguyên)
chieu_cao = 1.75 # float (số thực)
da_ket_hon = False # bool (luận lý)
khong_co = None # NoneType (giá trị rỗng)
Các kiểu dữ liệu cơ bản:
| Kiểu | Mô tả | Ví dụ |
|---|---|---|
int | Số nguyên | 42, -7, 0 |
float | Số thực | 3.14, -0.5 |
str | Chuỗi ký tự | "hello" |
bool | Đúng/Sai | True, False |
NoneType | Rỗng | None |
list | Danh sách | [1, 2, 3] |
tuple | Bộ bất biến | (1, 2) |
dict | Từ điển | {"k": "v"} |
set | Tập hợp | {1, 2, 3} |
Kiểm tra và ép kiểu:
x = "123"
print(type(x)) # <class 'str'>
y = int(x) # ép str -> int = 123
z = str(y) # ép int -> str = "123"
f = float("3.14") # = 3.14
print(isinstance(y, int)) # True - kiểm tra kiểu
Giải thích: type() trả về kiểu của biến; isinstance() kiểm tra biến có thuộc kiểu nào đó không (nên dùng cách này). Các hàm int(), str(), float() dùng để ép kiểu (type casting).
Quy tắc đặt tên biến:
- Chỉ chứa chữ, số, dấu
_; không bắt đầu bằng số. - Phân biệt hoa thường (
tuoi≠Tuoi). - Quy ước:
snake_casecho biến/hàm,PascalCasecho class,UPPER_CASEcho hằng số.
4. Toán tử (Operators)
Toán tử số học:
a, b = 10, 3
print(a + b) # 13 cộng
print(a - b) # 7 trừ
print(a * b) # 30 nhân
print(a / b) # 3.333... chia (luôn ra float)
print(a // b) # 3 chia lấy phần nguyên
print(a % b) # 1 chia lấy dư (modulo)
print(a ** b) # 1000 lũy thừa (10^3)
Toán tử so sánh (trả về bool): ==, !=, >, <, >=, <=
Toán tử luận lý:
x = True
y = False
print(x and y) # False - và (cả hai phải True)
print(x or y) # True - hoặc (một trong hai True)
print(not x) # False - phủ định
Toán tử gán rút gọn: +=, -=, *=, /=, //=, %=, **=
n = 5
n += 3 # tương đương n = n + 3 -> 8
Toán tử thành viên & định danh:
print("a" in "abc") # True - kiểm tra phần tử có trong chuỗi/list
print(3 not in [1, 2]) # True
print(None is None) # True - so sánh ĐỊNH DANH (cùng vùng nhớ)
Giải thích: Dùng is chỉ để so sánh với None, True, False. Dùng == để so sánh giá trị.
5. Chuỗi (Strings)
Định nghĩa: Chuỗi là dãy ký tự bất biến (immutable) — không thể thay đổi từng ký tự sau khi tạo.
s = "Python"
print(len(s)) # 6 - độ dài
print(s[0]) # 'P' - truy cập theo chỉ số (bắt đầu từ 0)
print(s[-1]) # 'n' - chỉ số âm tính từ cuối
print(s[0:3]) # 'Pyt' - slicing [start:end] (không gồm end)
print(s[::-1]) # 'nohtyP' - đảo ngược chuỗi
Giải thích slicing: Cú pháp s[start:end:step]. start mặc định 0, end mặc định hết chuỗi, step mặc định 1. [::-1] nghĩa là đi ngược bước -1.
Các phương thức chuỗi thông dụng:
text = " Hello World "
print(text.strip()) # "Hello World" - bỏ khoảng trắng 2 đầu
print(text.lower()) # " hello world "
print(text.upper()) # " HELLO WORLD "
print("a,b,c".split(",")) # ['a', 'b', 'c'] - tách chuỗi thành list
print("-".join(["a","b"])) # "a-b" - nối list thành chuỗi
print("hello".replace("l","L")) # "heLLo"
print("hello".startswith("he")) # True
print("hello".find("l")) # 2 - vị trí xuất hiện đầu tiên
f-string (định dạng chuỗi — cách hiện đại, khuyên dùng):
ten = "An"
tuoi = 25
print(f"Tôi là {ten}, {tuoi} tuổi") # Tôi là An, 25 tuổi
print(f"Tổng: {10 + 5}") # Tổng: 15 (chạy biểu thức)
print(f"Pi = {3.14159:.2f}") # Pi = 3.14 (làm tròn 2 số lẻ)
print(f"{1000000:,}") # 1,000,000 (dấu phân cách)
Giải thích: f-string (từ Python 3.6+) cho phép nhúng biểu thức trực tiếp vào chuỗi bằng {}. :.2f là định dạng — .2f nghĩa là số thực 2 chữ số thập phân.
6. Nhập / Xuất dữ liệu
# Nhập từ bàn phím (input luôn trả về str)
ten = input("Nhập tên: ")
tuoi = int(input("Nhập tuổi: ")) # ép sang int để tính toán
# Xuất với nhiều tham số
print("Tên:", ten, "- Tuổi:", tuoi)
print("a", "b", "c", sep="-") # a-b-c (sep: ký tự ngăn cách)
print("Không xuống dòng", end=" ") # end: ký tự kết thúc thay \n
Giải thích: input() luôn trả về chuỗi, nên cần ép kiểu int()/float() nếu muốn tính toán. sep và end là tham số tùy chọn của print().
7. Cấu trúc điều kiện
Định nghĩa: Cấu trúc điều kiện cho phép chương trình rẽ nhánh, thực thi khối lệnh khác nhau tùy điều kiện đúng/sai.
diem = 75
if diem >= 90:
print("Xuất sắc")
elif diem >= 70:
print("Khá")
elif diem >= 50:
print("Trung bình")
else:
print("Yếu")
Giải thích: Python kiểm tra if trước; nếu sai chuyển sang elif (else if); nếu tất cả sai thì chạy else. Khối lệnh được xác định bằng thụt lề (4 dấu cách).
Toán tử 3 ngôi (ternary):
trang_thai = "Đậu" if diem >= 50 else "Rớt"
match-case (Python 3.10+) — giống switch:
mau = "đỏ"
match mau:
case "đỏ":
print("Dừng lại")
case "vàng":
print("Chuẩn bị")
case "xanh":
print("Đi")
case _: # _ là trường hợp mặc định
print("Không xác định")
Giá trị "falsy" (được coi là False): 0, 0.0, "", [], {}, (), None, False.
ds = []
if not ds:
print("Danh sách rỗng") # in ra vì [] là falsy
8. Vòng lặp
Vòng lặp for — duyệt qua một dãy (iterable):
# Duyệt qua range
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 10, 2): # 2, 4, 6, 8 (start, stop, step)
print(i)
# Duyệt qua list
for ten in ["An", "Bình", "Cường"]:
print(ten)
# Duyệt kèm chỉ số bằng enumerate
for idx, ten in enumerate(["An", "Bình"]):
print(idx, ten) # 0 An / 1 Bình
# Duyệt 2 list song song bằng zip
for ten, tuoi in zip(["An", "Bình"], [25, 30]):
print(ten, tuoi)
Giải thích: range(n) tạo dãy số 0..n-1. enumerate() trả về cặp (chỉ số, giá trị). zip() ghép nhiều list lại theo từng cặp.
Vòng lặp while — lặp khi điều kiện còn đúng:
n = 5
while n > 0:
print(n)
n -= 1 # PHẢI cập nhật biến, nếu không sẽ lặp vô hạn
break và continue:
for i in range(10):
if i == 3:
continue # bỏ qua lần lặp này, sang lần kế
if i == 7:
break # thoát hẳn vòng lặp
print(i) # in 0,1,2,4,5,6
else trong vòng lặp (đặc trưng của Python):
for i in range(5):
if i == 10:
break
else:
print("Vòng lặp hoàn tất không bị break")
Giải thích: Khối else của vòng lặp chỉ chạy khi vòng lặp kết thúc tự nhiên (không bị break).
Phần II — Cấu Trúc Dữ Liệu
9. List (Danh sách)
Định nghĩa: List là tập hợp có thứ tự, thay đổi được (mutable), cho phép trùng lặp và chứa nhiều kiểu dữ liệu.
Trường hợp sử dụng: Lưu trữ tập hợp dữ liệu cần thay đổi, sắp xếp, duyệt — như danh sách sản phẩm, kết quả truy vấn.
fruits = ["táo", "cam", "xoài"]
# Truy cập & cập nhật
print(fruits[0]) # 'táo'
fruits[1] = "chuối" # thay đổi phần tử
# Thêm phần tử
fruits.append("dứa") # thêm cuối
fruits.insert(0, "lê") # chèn vào vị trí 0
fruits.extend(["nho", "ổi"]) # nối thêm nhiều phần tử
# Xóa phần tử
fruits.remove("cam") # xóa theo giá trị
last = fruits.pop() # xóa & trả về phần tử cuối
del fruits[0] # xóa theo chỉ số
# Các thao tác hữu ích
print(len(fruits)) # số phần tử
print("nho" in fruits) # kiểm tra tồn tại
fruits.sort() # sắp xếp tăng dần (đổi list gốc)
fruits.reverse() # đảo ngược
sorted_copy = sorted(fruits) # trả về list mới đã sắp xếp
Giải thích: append thêm 1 phần tử, extend thêm nhiều. sort() sửa list gốc còn sorted() trả bản sao mới. List hỗ trợ slicing giống chuỗi: fruits[1:3].
⚠️ Lưu ý gán tham chiếu:
a = [1, 2, 3]
b = a # b và a trỏ CÙNG list
b.append(4)
print(a) # [1, 2, 3, 4] - a cũng bị thay đổi!
c = a.copy() # tạo bản sao độc lập (shallow copy)
10. Tuple
Định nghĩa: Tuple giống list nhưng bất biến (immutable) — không thể thay đổi sau khi tạo.
Trường hợp sử dụng: Dữ liệu cố định không đổi (tọa độ, ngày tháng, trả về nhiều giá trị từ hàm), làm key của dictionary.
point = (10, 20)
print(point[0]) # 10
# point[0] = 5 # LỖI! tuple bất biến
# Unpacking (giải nén)
x, y = point # x=10, y=20
# Trả nhiều giá trị từ hàm thực chất là trả tuple
def min_max(nums):
return min(nums), max(nums)
lo, hi = min_max([3, 1, 9]) # lo=1, hi=9
# Tuple 1 phần tử cần dấu phẩy
single = (5,) # đây là tuple; (5) chỉ là số 5
Giải thích: Vì bất biến nên tuple nhanh hơn và an toàn hơn list khi dữ liệu không cần thay đổi. Unpacking giúp gán nhiều biến cùng lúc.
11. Set (Tập hợp)
Định nghĩa: Set là tập hợp không có thứ tự, không trùng lặp.
Trường hợp sử dụng: Loại bỏ trùng lặp, kiểm tra thành viên nhanh, các phép toán tập hợp (giao, hợp, hiệu).
s = {1, 2, 3, 3, 2}
print(s) # {1, 2, 3} - tự loại trùng
s.add(4) # thêm
s.discard(2) # xóa (không lỗi nếu không có)
# Loại trùng từ list
nums = [1, 1, 2, 3, 3]
unique = list(set(nums)) # [1, 2, 3]
# Phép toán tập hợp
a = {1, 2, 3}
b = {2, 3, 4}
print(a | b) # {1,2,3,4} hợp (union)
print(a & b) # {2,3} giao (intersection)
print(a - b) # {1} hiệu (difference)
print(a ^ b) # {1,4} hiệu đối xứng
Giải thích: Kiểm tra x in set rất nhanh (O(1)) so với list (O(n)) — nên dùng set khi cần kiểm tra tồn tại nhiều lần.
12. Dictionary (Từ điển)
Định nghĩa: Dictionary lưu dữ liệu dạng cặp key-value, có thể thay đổi, key là duy nhất.
Trường hợp sử dụng: Ánh xạ dữ liệu (id → tên), cấu hình, biểu diễn đối tượng JSON, đếm tần suất.
user = {
"name": "An",
"age": 25,
"email": "an@example.com"
}
# Truy cập
print(user["name"]) # 'An'
print(user.get("phone")) # None (không lỗi nếu thiếu key)
print(user.get("phone", "N/A")) # 'N/A' - giá trị mặc định
# Thêm / cập nhật
user["age"] = 26
user["city"] = "Hà Nội"
# Xóa
del user["email"]
age = user.pop("age") # xóa & trả giá trị
# Duyệt
for key in user: # duyệt key
print(key, user[key])
for key, value in user.items(): # duyệt cặp key-value
print(key, value)
print(list(user.keys())) # danh sách key
print(list(user.values())) # danh sách value
Giải thích: Dùng .get() thay vì [] khi không chắc key tồn tại để tránh lỗi KeyError. .items() trả về cả key và value.
Ví dụ: Đếm tần suất từ:
text = "a b a c b a"
count = {}
for word in text.split():
count[word] = count.get(word, 0) + 1
print(count) # {'a': 3, 'b': 2, 'c': 1}
13. Comprehensions
Định nghĩa: Comprehension là cú pháp ngắn gọn để tạo list/dict/set từ một iterable, thay cho vòng lặp dài.
Trường hợp sử dụng: Biến đổi/lọc dữ liệu một cách súc tích, dễ đọc.
# List comprehension
squares = [x**2 for x in range(5)] # [0,1,4,9,16]
# Có điều kiện lọc
evens = [x for x in range(10) if x % 2 == 0] # [0,2,4,6,8]
# Có if-else
labels = ["chẵn" if x%2==0 else "lẻ" for x in range(4)]
# Dict comprehension
sq_dict = {x: x**2 for x in range(4)} # {0:0,1:1,2:4,3:9}
# Set comprehension
unique_len = {len(w) for w in ["a","bb","cc"]} # {1, 2}
# Lồng nhau (ma trận)
matrix = [[i*j for j in range(3)] for i in range(3)]
Giải thích cú pháp: [biểu_thức for phần_tử in iterable if điều_kiện]. Đọc từ trái: "lấy biểu_thức, với mỗi phần_tử, nếu thỏa điều_kiện". Ngắn gọn và nhanh hơn vòng for thông thường.
Phần III — Hàm & Module
14. Hàm (Functions)
Định nghĩa: Hàm là khối code có tên, thực hiện một nhiệm vụ cụ thể, có thể tái sử dụng và nhận tham số.
Trường hợp sử dụng: Tránh lặp code (DRY), chia nhỏ chương trình, tăng khả năng đọc và bảo trì.
def chao(ten, loi_chao="Xin chào"): # ten: bắt buộc, loi_chao: mặc định
"""Trả về lời chào (đây là docstring mô tả hàm)."""
return f"{loi_chao}, {ten}!"
print(chao("An")) # Xin chào, An!
print(chao("Bình", "Hello")) # Hello, Bình!
print(chao(loi_chao="Hi", ten="C")) # gọi theo tên tham số (keyword)
Giải thích:
defđịnh nghĩa hàm,returntrả về kết quả (nếu không có thì trảNone).- Tham số mặc định (
loi_chao="...") cho phép gọi mà không cần truyền. - Docstring (chuỗi sau
def) mô tả hàm, truy cập quachao.__doc__.
Phạm vi biến (scope):
x = 10 # biến toàn cục (global)
def f():
y = 5 # biến cục bộ (local) - chỉ tồn tại trong hàm
print(x) # đọc được biến toàn cục
def g():
global x # khai báo dùng biến toàn cục để GHI
x = 20
Giải thích: Biến tạo trong hàm là cục bộ. Muốn sửa biến toàn cục bên trong hàm phải khai báo global.
15. Tham số nâng cao (*args, **kwargs)
Định nghĩa: *args nhận số lượng tham số vị trí tùy ý (gom thành tuple); **kwargs nhận số lượng tham số keyword tùy ý (gom thành dict).
Trường hợp sử dụng: Khi không biết trước số lượng tham số, viết hàm linh hoạt, hàm bao bọc (wrapper).
def tong(*args):
return sum(args)
print(tong(1, 2, 3, 4)) # 10 (args = (1,2,3,4))
def thong_tin(**kwargs):
for k, v in kwargs.items():
print(f"{k}: {v}")
thong_tin(ten="An", tuoi=25) # ten: An / tuoi: 25
def full(a, *args, **kwargs):
print(a, args, kwargs)
full(1, 2, 3, x=10, y=20) # 1 (2, 3) {'x': 10, 'y': 20}
# Unpacking khi GỌI hàm
nums = [1, 2, 3]
print(tong(*nums)) # giải nén list thành tham số
info = {"ten": "An", "tuoi": 25}
thong_tin(**info) # giải nén dict thành keyword
Giải thích: * gom/giải nén list/tuple, ** gom/giải nén dict. Thứ tự khai báo bắt buộc: tham số thường → *args → tham số keyword → **kwargs.
16. Lambda, map, filter, reduce
Lambda — hàm ẩn danh (một dòng):
binh_phuong = lambda x: x ** 2
print(binh_phuong(5)) # 25
cong = lambda a, b: a + b
print(cong(3, 4)) # 7
Giải thích: lambda tham_số: biểu_thức — dùng cho hàm nhỏ, đơn giản, thường truyền vào hàm khác.
map — áp dụng hàm cho mọi phần tử:
nums = [1, 2, 3, 4]
squared = list(map(lambda x: x**2, nums)) # [1,4,9,16]
filter — lọc phần tử thỏa điều kiện:
evens = list(filter(lambda x: x % 2 == 0, nums)) # [2, 4]
reduce — gộp dần thành 1 giá trị:
from functools import reduce
tong = reduce(lambda acc, x: acc + x, nums, 0) # 10
Giải thích: map biến đổi, filter lọc, reduce tích lũy. Tuy nhiên trong Python, comprehension thường dễ đọc hơn map/filter.
sorted với key:
nguoi = [("An", 25), ("Bình", 20), ("Cường", 30)]
theo_tuoi = sorted(nguoi, key=lambda x: x[1]) # sắp theo tuổi
17. Module & Package
Định nghĩa:
- Module: một file
.pychứa code (hàm, biến, class) có thể tái sử dụng. - Package: một thư mục chứa nhiều module (thường có file
__init__.py).
Trường hợp sử dụng: Tổ chức code thành nhiều file, chia sẻ & tái sử dụng, dùng thư viện bên thứ ba.
# Cách import
import math
print(math.sqrt(16)) # 4.0
from math import sqrt, pi # import cụ thể
print(sqrt(25), pi)
from math import sqrt as can_bac # đổi tên (alias)
print(can_bac(9))
import math as m # alias module
Tạo module riêng — file mymath.py:
# mymath.py
PI = 3.14159
def square(x):
return x * x
# main.py
import mymath
print(mymath.square(5)) # 25
print(mymath.PI)
if __name__ == "__main__":
def main():
print("Chạy chương trình chính")
if __name__ == "__main__":
main()
Giải thích: Khi chạy file trực tiếp, __name__ == "__main__". Khi file được import vào file khác, __name__ là tên module. Mẫu này giúp code chỉ chạy khi thực thi trực tiếp, không chạy khi bị import.
18. Xử lý ngoại lệ (Exceptions)
Định nghĩa: Ngoại lệ (exception) là lỗi xảy ra trong lúc chạy. Xử lý ngoại lệ giúp chương trình không bị dừng đột ngột mà phản ứng linh hoạt.
Trường hợp sử dụng: Đọc file không tồn tại, chia cho 0, ép kiểu sai, lỗi mạng, lỗi nhập liệu người dùng.
try:
so = int(input("Nhập số: "))
ket_qua = 10 / so
except ValueError:
print("Không phải số hợp lệ!")
except ZeroDivisionError:
print("Không thể chia cho 0!")
except Exception as e:
print(f"Lỗi khác: {e}")
else:
print(f"Kết quả: {ket_qua}") # chạy khi KHÔNG có lỗi
finally:
print("Luôn chạy dù có lỗi hay không")
Giải thích:
try: khối code có thể gây lỗi.except: bắt và xử lý từng loại lỗi cụ thể.else: chạy khi không có lỗi.finally: luôn chạy (dùng để dọn dẹp tài nguyên).
Tự ném ngoại lệ (raise):
def rut_tien(so_du, so_tien):
if so_tien > so_du:
raise ValueError("Số dư không đủ")
return so_du - so_tien
Tự định nghĩa exception:
class SoDuKhongDu(Exception):
"""Ngoại lệ khi số dư không đủ."""
pass
raise SoDuKhongDu("Tài khoản không đủ tiền")
Giải thích: Tạo class kế thừa Exception để có loại lỗi riêng, dễ phân biệt và xử lý.
19. Làm việc với File
Định nghĩa: Python đọc/ghi file qua hàm open(). Nên dùng with để file tự động đóng.
# Ghi file (w: ghi đè, a: ghi nối)
with open("data.txt", "w", encoding="utf-8") as f:
f.write("Dòng 1\n")
f.write("Dòng 2\n")
# Đọc toàn bộ
with open("data.txt", "r", encoding="utf-8") as f:
noi_dung = f.read()
# Đọc từng dòng (tiết kiệm bộ nhớ với file lớn)
with open("data.txt", "r", encoding="utf-8") as f:
for dong in f:
print(dong.strip())
# Đọc thành list các dòng
with open("data.txt", encoding="utf-8") as f:
dong_list = f.readlines()
Giải thích: with đảm bảo file luôn được đóng dù có lỗi (context manager). Luôn dùng encoding="utf-8" để xử lý tiếng Việt. Chế độ: r đọc, w ghi đè, a ghi nối, r+ đọc-ghi, thêm b cho file nhị phân (rb, wb).
Làm việc với JSON:
import json
data = {"ten": "An", "tuoi": 25, "so_thich": ["đọc", "code"]}
# Ghi dict ra file JSON
with open("user.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# Đọc JSON về dict
with open("user.json", encoding="utf-8") as f:
data2 = json.load(f)
# Chuyển đổi chuỗi <-> dict
chuoi = json.dumps(data, ensure_ascii=False)
obj = json.loads(chuoi)
Giải thích: dump/load làm việc với file; dumps/loads (có s = string) làm việc với chuỗi. ensure_ascii=False giữ nguyên tiếng Việt thay vì mã hóa \u.
Phần IV — Lập Trình Hướng Đối Tượng (OOP)
20. Class & Object
Định nghĩa:
- Class (lớp): khuôn mẫu định nghĩa thuộc tính (dữ liệu) và phương thức (hành vi).
- Object (đối tượng): một thể hiện cụ thể được tạo từ class.
Trường hợp sử dụng: Mô hình hóa thực thể thế giới thực (User, SảnPhẩm, ĐơnHàng), tổ chức code phức tạp, tái sử dụng.
class SinhVien:
truong = "Đại học ABC" # thuộc tính lớp (dùng chung mọi object)
def __init__(self, ten, tuoi): # hàm khởi tạo (constructor)
self.ten = ten # thuộc tính thể hiện (riêng mỗi object)
self.tuoi = tuoi
def gioi_thieu(self): # phương thức
return f"Tôi là {self.ten}, {self.tuoi} tuổi, học {self.truong}"
# Tạo object
sv1 = SinhVien("An", 20)
sv2 = SinhVien("Bình", 22)
print(sv1.gioi_thieu())
print(sv1.ten) # truy cập thuộc tính
sv1.tuoi = 21 # sửa thuộc tính
Giải thích:
__init__tự động chạy khi tạo object, dùng để khởi tạo thuộc tính.selflà tham số đầu tiên của mọi phương thức, trỏ tới chính object đang gọi.- Thuộc tính lớp (
truong) dùng chung; thuộc tính thể hiện (self.ten) riêng từng object.
21. Kế thừa (Inheritance)
Định nghĩa: Kế thừa cho phép một class (con) thừa hưởng thuộc tính và phương thức từ class khác (cha), và có thể mở rộng/ghi đè.
Trường hợp sử dụng: Tái sử dụng code chung, mô hình quan hệ "là một" (Chó là một Động vật).
class DongVat:
def __init__(self, ten):
self.ten = ten
def keu(self):
return "..."
def mo_ta(self):
return f"{self.ten} kêu {self.keu()}"
class Cho(DongVat): # Cho kế thừa DongVat
def keu(self): # ghi đè (override) phương thức
return "Gâu gâu"
class Meo(DongVat):
def __init__(self, ten, mau):
super().__init__(ten) # gọi __init__ của lớp cha
self.mau = mau
def keu(self):
return "Meo meo"
c = Cho("Lucky")
print(c.mo_ta()) # Lucky kêu Gâu gâu
m = Meo("Mimi", "trắng")
print(m.mo_ta()) # Mimi kêu Meo meo
Giải thích:
class Con(Cha): khai báo kế thừa.super(): gọi đến lớp cha (thường dùng trong__init__để khởi tạo phần của cha).- Override: định nghĩa lại phương thức của cha trong lớp con.
Đa kế thừa:
class A:
def hello(self): return "A"
class B:
def world(self): return "B"
class C(A, B): # kế thừa cả A và B
pass
22. Đa hình & Đóng gói
Đa hình (Polymorphism): cùng một phương thức nhưng cho kết quả khác nhau tùy đối tượng.
dong_vat = [Cho("Lucky"), Meo("Mimi", "đen")]
for dv in dong_vat:
print(dv.keu()) # mỗi loại kêu khác nhau, dù gọi cùng tên hàm
Giải thích: Đa hình cho phép xử lý nhiều loại object qua cùng một giao diện chung, không cần biết kiểu cụ thể.
Đóng gói (Encapsulation): ẩn dữ liệu nội bộ, kiểm soát truy cập.
class TaiKhoan:
def __init__(self):
self.ten = "An" # public - truy cập tự do
self._so_dien_thoai = "" # protected - quy ước "nội bộ" (1 gạch dưới)
self.__so_du = 1000 # private - bị "bóp méo tên" (2 gạch dưới)
def xem_so_du(self):
return self.__so_du # truy cập qua phương thức công khai
tk = TaiKhoan()
print(tk.ten) # OK
# print(tk.__so_du) # LỖI! private
print(tk.xem_so_du()) # 1000 - đúng cách
Giải thích: Python không có private thực sự. Quy ước: _x là "nội bộ, đừng đụng vào", __x bị đổi tên thành _TenClass__x (name mangling) để tránh truy cập trực tiếp.
23. Magic Methods (Dunder)
Định nghĩa: Magic methods (dunder = "double underscore") là các phương thức đặc biệt bắt đầu/kết thúc bằng __, cho phép object tương tác với cú pháp built-in của Python.
Trường hợp sử dụng: Tùy biến cách object hiển thị, so sánh, cộng trừ, làm việc với len(), [], v.v.
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self): # khi dùng print() / str()
return f"Vector({self.x}, {self.y})"
def __repr__(self): # biểu diễn cho debug
return f"Vector({self.x!r}, {self.y!r})"
def __add__(self, other): # toán tử +
return Vector(self.x + other.x, self.y + other.y)
def __eq__(self, other): # toán tử ==
return self.x == other.x and self.y == other.y
def __len__(self): # hàm len()
return 2
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2) # Vector(4, 6) - nhờ __add__
print(v1 == Vector(1, 2)) # True - nhờ __eq__
print(str(v1)) # Vector(1, 2) - nhờ __str__
Giải thích:
__str__: chuỗi thân thiện cho người dùng (print).__repr__: chuỗi rõ ràng cho lập trình viên (debug, REPL).__add__,__sub__,__mul__...: định nghĩa toán tử.__eq__,__lt__...: định nghĩa so sánh.__len__,__getitem__: cho phép dùnglen(),obj[i].
24. Property, Classmethod, Staticmethod
@property — biến phương thức thành thuộc tính, có kiểm soát:
class HinhTron:
def __init__(self, ban_kinh):
self._ban_kinh = ban_kinh
@property
def ban_kinh(self): # getter
return self._ban_kinh
@ban_kinh.setter
def ban_kinh(self, value): # setter có kiểm tra
if value < 0:
raise ValueError("Bán kính không âm")
self._ban_kinh = value
@property
def dien_tich(self): # thuộc tính tính toán
return 3.14159 * self._ban_kinh ** 2
c = HinhTron(5)
print(c.ban_kinh) # 5 - gọi như thuộc tính (không có ())
print(c.dien_tich) # 78.5...
c.ban_kinh = 10 # gọi setter
# c.ban_kinh = -1 # LỖI ValueError
Giải thích: @property cho phép truy cập phương thức như thuộc tính, hữu ích để tính toán động hoặc kiểm tra giá trị khi gán.
@classmethod và @staticmethod:
class NguoiDung:
so_luong = 0
def __init__(self, ten):
self.ten = ten
NguoiDung.so_luong += 1
@classmethod
def tu_chuoi(cls, chuoi): # nhận cls (class), tạo object kiểu khác
return cls(chuoi.strip())
@staticmethod
def la_ten_hop_le(ten): # không nhận self/cls - hàm tiện ích
return len(ten) > 0
Giải thích:
- Instance method: nhận
self, thao tác với object cụ thể. @classmethod: nhậncls, thao tác với class (hay dùng làm "factory" tạo object).@staticmethod: không nhậnself/cls, chỉ là hàm tiện ích gắn với class.
Phần V — Nâng Cao
25. Iterator & Generator
Iterator — định nghĩa: Đối tượng có thể duyệt từng phần tử, cài đặt __iter__() và __next__().
nums = [1, 2, 3]
it = iter(nums) # tạo iterator
print(next(it)) # 1
print(next(it)) # 2
# next() liên tục cho đến khi hết -> StopIteration
Generator — định nghĩa: Hàm dùng yield thay return để sinh ra dãy giá trị lười (lazy), không lưu toàn bộ vào bộ nhớ.
Trường hợp sử dụng: Xử lý dữ liệu rất lớn/vô hạn, đọc file khổng lồ, pipeline dữ liệu — tiết kiệm bộ nhớ.
def dem_so(n):
i = 0
while i < n:
yield i # "trả về tạm" và tạm dừng, lần sau chạy tiếp
i += 1
for x in dem_so(5):
print(x) # 0 1 2 3 4
# Generator expression (giống list comp nhưng dùng ngoặc tròn)
binh_phuong = (x**2 for x in range(1000000)) # không tốn bộ nhớ
print(next(binh_phuong)) # 0
Giải thích:
yieldtạm dừng hàm, ghi nhớ trạng thái, lần gọinext()tiếp theo chạy tiếp từ chỗ dừng.- Generator chỉ tạo giá trị khi cần → tiết kiệm bộ nhớ cực lớn với dữ liệu khổng lồ.
- So sánh:
[x for x in ...]tạo cả list trong RAM;(x for x in ...)tạo từng phần tử khi duyệt.
26. Decorator
Định nghĩa: Decorator là hàm nhận một hàm khác làm tham số, "bọc" thêm hành vi rồi trả về hàm mới — mà không sửa code hàm gốc.
Trường hợp sử dụng: Logging, đo thời gian, kiểm tra quyền, cache, validate — các "khía cạnh ngang" (cross-cutting concerns).
import functools
import time
def do_thoi_gian(func):
@functools.wraps(func) # giữ tên & docstring hàm gốc
def wrapper(*args, **kwargs):
bat_dau = time.time()
ket_qua = func(*args, **kwargs) # gọi hàm gốc
print(f"{func.__name__} chạy {time.time()-bat_dau:.4f}s")
return ket_qua
return wrapper
@do_thoi_gian # áp dụng decorator
def tinh_tong(n):
return sum(range(n))
tinh_tong(1000000) # tự động in thời gian chạy
Giải thích:
@do_thoi_giantương đươngtinh_tong = do_thoi_gian(tinh_tong).wrapperthực hiện logic thêm (đo giờ) rồi gọi hàm gốcfunc.*args, **kwargscho phép wrapper hoạt động với hàm có tham số bất kỳ.@functools.wrapsgiữ lại metadata (tên, docstring) của hàm gốc.
Decorator có tham số:
def lap_lai(so_lan):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(so_lan):
func(*args, **kwargs)
return wrapper
return decorator
@lap_lai(3)
def chao():
print("Hi")
chao() # in "Hi" 3 lần
Giải thích: Cần 3 tầng hàm: tầng ngoài nhận tham số, tầng giữa nhận hàm, tầng trong là wrapper.
27. Context Manager (with)
Định nghĩa: Context manager quản lý tài nguyên (mở/đóng), đảm bảo dọn dẹp đúng cách qua with. Cài đặt __enter__ và __exit__.
Trường hợp sử dụng: Mở file, kết nối DB, khóa (lock), transaction — cần đảm bảo luôn được đóng/giải phóng.
class KetNoi:
def __enter__(self):
print("Mở kết nối")
return self # giá trị gán cho 'as'
def __exit__(self, exc_type, exc_val, exc_tb):
print("Đóng kết nối") # luôn chạy, kể cả khi lỗi
return False # False: không nuốt ngoại lệ
with KetNoi() as conn:
print("Đang làm việc...")
# Tự động: Mở kết nối -> Đang làm việc -> Đóng kết nối
Cách ngắn gọn bằng contextlib:
from contextlib import contextmanager
@contextmanager
def mo_ket_noi():
print("Mở")
try:
yield "connection" # phần trước yield = __enter__
finally:
print("Đóng") # phần sau yield = __exit__
with mo_ket_noi() as conn:
print("Làm việc với", conn)
Giải thích: __enter__ chạy khi vào with, __exit__ chạy khi ra (kể cả khi có lỗi). Đây là lý do with open(...) luôn đóng file an toàn.
28. Type Hints
Định nghĩa: Type hints (gợi ý kiểu, từ Python 3.5+) cho phép chú thích kiểu dữ liệu của biến, tham số, giá trị trả về. Không bắt buộc khi chạy nhưng giúp IDE/công cụ kiểm tra lỗi.
Trường hợp sử dụng: Dự án lớn, làm việc nhóm, tăng độ rõ ràng, bắt lỗi sớm bằng mypy.
def chao(ten: str, tuoi: int = 0) -> str:
return f"{ten}, {tuoi} tuổi"
tuoi: int = 25
ten: str = "An"
diem: list[float] = [8.5, 9.0]
user: dict[str, int] = {"age": 25}
from typing import Optional, Union
def tim(id: int) -> Optional[str]: # trả str hoặc None
return None
def xu_ly(x: Union[int, str]) -> str: # int HOẶC str (Python 3.10+: int | str)
return str(x)
Giải thích:
ten: strchú thích biến/tham số kiểu str.-> strchú thích kiểu trả về.Optional[str]=str | None.Union[a, b]= một trong hai kiểu.- Python không ép buộc type hints lúc chạy — cần
mypy/pyrightđể kiểm tra.
29. Lập trình bất đồng bộ (async/await)
Định nghĩa: Lập trình bất đồng bộ cho phép một luồng xử lý nhiều tác vụ "chờ" (I/O) đồng thời mà không chặn (blocking) — bằng async def và await.
Trường hợp sử dụng: Gọi nhiều API, đọc/ghi mạng, DB, web server hiệu năng cao (nhiều tác vụ I/O-bound).
import asyncio
async def tai_du_lieu(ten, giay):
print(f"Bắt đầu tải {ten}")
await asyncio.sleep(giay) # mô phỏng chờ I/O (không chặn)
print(f"Xong {ten}")
return f"data-{ten}"
async def main():
# Chạy SONG SONG nhiều coroutine
ket_qua = await asyncio.gather(
tai_du_lieu("A", 2),
tai_du_lieu("B", 1),
tai_du_lieu("C", 3),
)
print(ket_qua)
asyncio.run(main()) # tổng thời gian ~3s thay vì 2+1+3=6s
Giải thích:
async defđịnh nghĩa coroutine (hàm bất đồng bộ).awaittạm nhường quyền điều khiển khi gặp tác vụ chờ, để việc khác chạy.asyncio.gather()chạy nhiều coroutine song song.asyncio.run()khởi động vòng lặp sự kiện (event loop).
⚠️ Lưu ý: async chỉ tăng tốc tác vụ I/O-bound (chờ mạng/đĩa), KHÔNG tăng tốc tác vụ CPU-bound (tính toán nặng) — việc đó dùng đa tiến trình.
30. Đa luồng & Đa tiến trình
Lý thuyết — GIL: Python có GIL (Global Interpreter Lock) — chỉ cho 1 luồng chạy bytecode Python tại một thời điểm. Do đó:
- Threading (đa luồng): tốt cho I/O-bound (chờ mạng, file).
- Multiprocessing (đa tiến trình): tốt cho CPU-bound (tính toán nặng), vượt qua GIL bằng cách tạo nhiều tiến trình riêng.
Threading:
import threading
def cong_viec(ten):
print(f"Luồng {ten} chạy")
threads = []
for i in range(3):
t = threading.Thread(target=cong_viec, args=(i,))
t.start()
threads.append(t)
for t in threads:
t.join() # đợi tất cả luồng xong
Multiprocessing:
from multiprocessing import Pool
def binh_phuong(x):
return x * x
if __name__ == "__main__":
with Pool(4) as p: # 4 tiến trình
ket_qua = p.map(binh_phuong, [1,2,3,4,5])
print(ket_qua) # [1, 4, 9, 16, 25]
Cách hiện đại — concurrent.futures:
from concurrent.futures import ThreadPoolExecutor
def tai(url):
return f"Đã tải {url}"
with ThreadPoolExecutor(max_workers=3) as executor:
ket_qua = list(executor.map(tai, ["a", "b", "c"]))
Giải thích: ThreadPoolExecutor cho I/O-bound, ProcessPoolExecutor cho CPU-bound. API thống nhất, dễ dùng hơn threading/multiprocessing thô.
31. Thư viện chuẩn hữu ích
collections — cấu trúc dữ liệu nâng cao:
from collections import Counter, defaultdict, namedtuple, deque
# Counter - đếm tần suất
print(Counter("aabbbc")) # Counter({'b': 3, 'a': 2, 'c': 1})
# defaultdict - dict có giá trị mặc định
d = defaultdict(list)
d["a"].append(1) # không lỗi dù key chưa tồn tại
# namedtuple - tuple có tên trường
Point = namedtuple("Point", ["x", "y"])
p = Point(1, 2)
print(p.x, p.y)
# deque - hàng đợi 2 đầu (thêm/xóa 2 đầu nhanh)
dq = deque([1, 2, 3])
dq.appendleft(0) # thêm đầu
dq.append(4) # thêm cuối
datetime — ngày giờ:
from datetime import datetime, timedelta
now = datetime.now()
print(now.strftime("%d/%m/%Y %H:%M")) # định dạng
mai = now + timedelta(days=1) # cộng 1 ngày
os & pathlib — đường dẫn & hệ thống:
from pathlib import Path
p = Path("docs/file.txt")
print(p.name) # file.txt
print(p.suffix) # .txt
print(p.parent) # docs
print(p.exists()) # kiểm tra tồn tại
for f in Path(".").glob("*.py"): # tìm tất cả file .py
print(f)
itertools — công cụ lặp:
from itertools import chain, combinations, product, count
print(list(chain([1,2],[3,4]))) # [1,2,3,4] nối
print(list(combinations([1,2,3], 2))) # tổ hợp chập 2
re — biểu thức chính quy (regex):
import re
email = "lien he: an@example.com"
match = re.search(r"\w+@\w+\.\w+", email)
print(match.group()) # an@example.com
32. Best Practices & Mẹo
PEP 8 — Quy chuẩn code Python:
- Thụt lề 4 dấu cách (không dùng tab).
- Đặt tên:
snake_case(biến/hàm),PascalCase(class),UPPER_CASE(hằng). - Dòng tối đa ~79-99 ký tự.
- Khoảng trắng quanh toán tử:
x = 1(khôngx=1). - Dùng công cụ
black(format),flake8/ruff(lint).
Mẹo Pythonic:
# Hoán đổi không cần biến tạm
a, b = b, a
# Gán nhiều biến
x = y = z = 0
# Kiểm tra rỗng đúng cách
if not my_list: # thay vì len(my_list) == 0
...
# Dùng enumerate thay vì range(len(...))
for i, v in enumerate(items):
...
# Nối chuỗi list hiệu quả
ket_qua = "".join(danh_sach_chuoi) # thay vì += trong vòng lặp
# Walrus operator := (Python 3.8+) - gán trong biểu thức
if (n := len(data)) > 10:
print(f"Quá dài: {n}")
# Giá trị mặc định an toàn (KHÔNG dùng list/dict làm default!)
def f(items=None):
if items is None:
items = [] # tạo mới mỗi lần gọi
Lỗi thường gặp cần tránh:
- ❌ Dùng
list/dictlàm tham số mặc định (chúng được chia sẻ giữa các lần gọi → bug). - ❌ So sánh
isvới chuỗi/số (dùng==). - ❌ Bắt
except:trống (nuốt hết lỗi, khó debug) — hãy bắt loại cụ thể. - ❌ Sửa list trong khi đang duyệt nó.
Công cụ chất lượng code:
pip install black ruff mypy pytest
black . # tự động format
ruff check . # phát hiện lỗi style/logic
mypy . # kiểm tra type hints
pytest # chạy test
Tổng kết
| Cấp độ | Chủ đề chính |
|---|---|
| Cơ bản | Biến, kiểu dữ liệu, điều kiện, vòng lặp, chuỗi |
| Cấu trúc dữ liệu | List, tuple, set, dict, comprehension |
| Hàm & Module | Hàm, *args/**kwargs, lambda, module, exception, file |
| OOP | Class, kế thừa, đa hình, magic methods, property |
| Nâng cao | Generator, decorator, context manager, type hints, async, concurrency |
Lộ trình học gợi ý:
- Nắm vững Phần I & II → viết được script cơ bản.
- Học Phần III → tổ chức code thành hàm/module, xử lý lỗi & file.
- Học Phần IV (OOP) → xây dựng ứng dụng có cấu trúc.
- Học Phần V khi cần tối ưu hiệu năng hoặc làm dự án lớn.