ds = ["A", "B", "C", "D", "E"]print(ds[0]) # A — phần tử đầuprint(ds[2]) # Cprint(ds[-1]) # E — phần tử cuốiprint(ds[-2]) # Dprint(len(ds)) # 5print(ds[5]) # ❌ IndexError: list index out of range
Bạn đã học slicing với chuỗi ở 05 - Chuỗi ký tự (String). Cú pháp hoàn toàn giống nhau với list, tuple, và mọi dãy dữ liệu khác. Đây là ví dụ điển hình cho triết lý “một cách hiển nhiên để làm việc đó” của Python.
2.3. List lồng nhau (nested list)
ma_tran = [ [1, 2, 3], [4, 5, 6], [7, 8, 9],]print(ma_tran[0]) # [1, 2, 3]print(ma_tran[0][1]) # 2print(ma_tran[2][2]) # 9# Duyệt ma trậnfor hang in ma_tran: for o in hang: print(f"{o:>4}", end="") print()
a = [1, 2, 3]a.append([4, 5])print(a) # [1, 2, 3, [4, 5]] <- thêm CẢ LIST làm 1 phần tửb = [1, 2, 3]b.extend([4, 5])print(b) # [1, 2, 3, 4, 5] <- thêm TỪNG phần tử
Nối list bằng +
a = [1, 2]b = [3, 4]c = a + bprint(c) # [1, 2, 3, 4]print(a) # [1, 2] — a KHÔNG đổi, `+` tạo list mới
a.extend(b)
a + b
Sửa a
Có
Không
Trả về
None
List mới
5. Xóa phần tử
Cách
Tác dụng
Trả về
remove(x)
Xóa phần tử có giá trịx đầu tiên
None
pop(i)
Xóa phần tử ở vị tríi (mặc định cuối)
Phần tử bị xóa
del ds[i]
Xóa theo vị trí hoặc đoạn
—
clear()
Xóa sạch
None
ds = ["A", "B", "C", "B", "D"]ds.remove("B")print(ds) # ['A', 'C', 'B', 'D'] — chỉ xóa "B" ĐẦU TIÊNx = ds.pop()print(x, ds) # D ['A', 'C', 'B'] — pop trả về phần tử bị xóay = ds.pop(0)print(y, ds) # A ['C', 'B']del ds[0]print(ds) # ['B']ds.clear()print(ds) # []
ds = ["A", "B", "C"]ds.remove("Z") # ❌ ValueError: list.remove(x): x not in list
Cách an toàn:
if "Z" in ds: ds.remove("Z")
⚠️ Đừng xóa phần tử khi đang duyệt
# ❌ SAI — kết quả không như mong đợids = [1, 2, 3, 4, 5, 6]for x in ds: if x % 2 == 0: ds.remove(x)print(ds) # [1, 3, 5] ... nhưng đôi khi ra kết quả sai!
Khi xóa phần tử, các chỉ số phía sau bị dịch chuyển, khiến vòng lặp bỏ sót.
# ✅ ĐÚNG — tạo list mớids = [1, 2, 3, 4, 5, 6]ds = [x for x in ds if x % 2 != 0]print(ds) # [1, 3, 5]# ✅ Hoặc duyệt trên bản saods = [1, 2, 3, 4, 5, 6]for x in ds[:]: # ds[:] tạo bản sao if x % 2 == 0: ds.remove(x)
6. Các method khác
ds = [30, 10, 50, 20, 10, 40]print(len(ds)) # 6print(ds.count(10)) # 2 — đếm số lần xuất hiệnprint(ds.index(50)) # 2 — vị trí đầu tiênprint(min(ds)) # 10print(max(ds)) # 50print(sum(ds)) # 160print(sum(ds) / len(ds)) # 26.666666666666668# Sắp xếp TẠI CHỖ (sửa list gốc)ds.sort()print(ds) # [10, 10, 20, 30, 40, 50]ds.sort(reverse=True)print(ds) # [50, 40, 30, 20, 10, 10]# Đảo ngược tại chỗds.reverse()print(ds) # [10, 10, 20, 30, 40, 50]
⚠️ sort() vs sorted()
ds = [3, 1, 2]ds.sort() # Sửa ds, trả về Noneprint(ds) # [1, 2, 3]ds2 = [3, 1, 2]moi = sorted(ds2) # KHÔNG sửa ds2, trả về list mớiprint(ds2, moi) # [3, 1, 2] [1, 2, 3]
ds = ["banana", "Apple", "cherry"]print(sorted(ds)) # ['Apple', 'banana', 'cherry'] — hoa trướcprint(sorted(ds, key=str.lower)) # ['Apple', 'banana', 'cherry'] — không phân biệt hoa thườngprint(sorted(ds, key=len)) # ['Apple', 'banana', 'cherry'] — theo độ dài
khach_hang = [ {"ten": "Nguyen Van A", "no": 5_000_000}, {"ten": "Tran Thi B", "no": 12_000_000}, {"ten": "Le Van C", "no": 800_000},]# Sắp xếp theo công nợ giảm dần — tương đương ORDER BY No DESCtheo_no = sorted(khach_hang, key=lambda kh: kh["no"], reverse=True)for kh in theo_no: print(f"{kh['ten']:<20}{kh['no']:>15,}")
lambda là gì?
lambda kh: kh["no"] là một hàm ẩn danh (anonymous function) — hàm nhỏ viết trên một dòng. Nó nói: “với mỗi phần tử kh, hãy lấy giá trị kh["no"] để làm khóa sắp xếp”. Bạn sẽ học kỹ ở 10 - Hàm (Function). Giờ chỉ cần nhận biết mẫu này.
PHẦN 2 — TUẦN 13
7. ⚠️ Bẫy tham chiếu (Reference Trap)
Đây là chủ đề quan trọng nhất của module này. Rất nhiều lỗi khó hiểu bắt nguồn từ đây.
7.1. Gán list KHÔNG tạo bản sao
a = [1, 2, 3]b = a # b KHÔNG phải bản sao, b là NHÃN THỨ HAI dán lên cùng đối tượngb.append(4)print(b) # [1, 2, 3, 4]print(a) # [1, 2, 3, 4] <- a CŨNG ĐỔI!
Nhớ lại khái niệm “biến là nhãn dán” ở 03 - Biến và kiểu dữ liệu. Với số và chuỗi (immutable), điều này không gây hậu quả. Với list (mutable), nó gây ra lỗi rất khó tìm.
Kiểm chứng:
a = [1, 2, 3]b = aprint(a is b) # True — cùng một đối tượngprint(id(a) == id(b)) # True
7.2. Cách tạo bản sao thật (shallow copy)
a = [1, 2, 3]b1 = a.copy() # Cách 1 — rõ ràng nhấtb2 = a[:] # Cách 2 — slicingb3 = list(a) # Cách 3 — ép kiểub1.append(4)print(a) # [1, 2, 3] — a không đổi ✅
7.3. Shallow copy chưa đủ với list lồng nhau
a = [[1, 2], [3, 4]]b = a.copy()b[0].append(99)print(a) # [[1, 2, 99], [3, 4]] <- a VẪN ĐỔI!
Shallow copy (sao chép nông) chỉ sao chép lớp ngoài. Các list bên trong vẫn được chia sẻ.
List lồng nhau (chứa list, dict) → dùng copy.deepcopy()
8. List Comprehension
List comprehension là cú pháp tạo list ngắn gọn — đặc trưng nổi tiếng nhất của Python.
8.1. Cú pháp cơ bản
[<biểu thức> for <biến> in <dãy>]
# ❌ Cách dàibinh_phuong = []for i in range(1, 6): binh_phuong.append(i ** 2)print(binh_phuong) # [1, 4, 9, 16, 25]# ✅ List comprehensionbinh_phuong = [i ** 2 for i in range(1, 6)]print(binh_phuong) # [1, 4, 9, 16, 25]
8.2. Có điều kiện lọc
[<biểu thức> for <biến> in <dãy> if <điều kiện>]
so_chan = [i for i in range(1, 21) if i % 2 == 0]print(so_chan) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
Tương đương SQL:
SELECT So FROM Bang WHERE So % 2 = 0
8.3. Có if/else trong biểu thức
# Chú ý: if/else đứng TRƯỚC forket_qua = ["Chan" if i % 2 == 0 else "Le" for i in range(1, 6)]print(ket_qua) # ['Le', 'Chan', 'Le', 'Chan', 'Le']
Vị trí
Vai trò
[x for x in ds if <đk>]
Lọc — chỉ giữ phần tử thỏa mãn
[a if <đk> else b for x in ds]
Biến đổi — mọi phần tử đều giữ, chỉ đổi giá trị
8.4. Ví dụ thực tế
# Ép kiểu hàng loạtchuoi_so = ["10", "20", "30"]so = [int(x) for x in chuoi_so]print(so) # [10, 20, 30]# Chuẩn hóa dữ liệuten_tho = [" nguyen van a ", "TRAN THI B", "le van c"]ten_chuan = [t.strip().title() for t in ten_tho]print(ten_chuan) # ['Nguyen Van A', 'Tran Thi B', 'Le Van C']# Lọc + biến đổigia = [100000, 250000, 80000, 500000]gia_giam = [g * 0.9 for g in gia if g >= 100000]print(gia_giam) # [90000.0, 225000.0, 450000.0]# Trích một cột từ list các dict — tương đương SELECT Ten FROM ...kh = [ {"ma": "KH001", "ten": "A", "no": 5_000_000}, {"ma": "KH002", "ten": "B", "no": 0},]ten_kh = [x["ten"] for x in kh]print(ten_kh) # ['A', 'B']# Lọc + trích — SELECT Ma FROM KH WHERE No > 0co_no = [x["ma"] for x in kh if x["no"] > 0]print(co_no) # ['KH001']
8.5. Lồng nhau
ma_tran = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]# Làm phẳng ma trậnphang = [o for hang in ma_tran for o in hang]print(phang) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Thứ tự đọc: giống hệt vòng lặp lồng nhau viết theo hàng ngang.
Đừng lạm dụng
List comprehension chỉ nên dùng khi nó dễ đọc hơn vòng lặp thường. Nếu phải lồng 3 cấp hoặc điều kiện phức tạp, hãy dùng vòng lặp for bình thường.
# ❌ Quá khó đọckq = [x*y for x in range(10) if x%2==0 for y in range(10) if y%3==0 if x+y>5]
9. Tuple
Tuple là dãy có thứ tự nhưng immutable (không sửa được).
9.1. Tạo tuple
t = (1, 2, 3)t2 = 1, 2, 3 # Dấu ngoặc có thể bỏt3 = () # Tuple rỗngt4 = (5,) # ⚠️ Tuple 1 phần tử — BẮT BUỘC có dấu phẩyt5 = (5) # ❌ Đây là số 5, không phải tuple!print(type(t4)) # <class 'tuple'>print(type(t5)) # <class 'int'>
9.2. Truy cập — giống list
t = ("A", "B", "C")print(t[0]) # Aprint(t[-1]) # Cprint(t[0:2]) # ('A', 'B')print(len(t)) # 3print("B" in t) # True
9.3. Không sửa được
t = (1, 2, 3)t[0] = 99 # ❌ TypeError: 'tuple' object does not support item assignmentt.append(4) # ❌ AttributeError: 'tuple' object has no attribute 'append'
"""Quản lý danh mục hàng hóa — tổng hợp M02 đến M08."""# Dữ liệu: mỗi mặt hàng là một tuple (ma, ten, don_vi, so_luong, don_gia)hang_hoa = [ ("VT001", "Ban lam viec", "Cai", 3, 2_500_000), ("VT002", "Ghe xoay", "Cai", 10, 850_000), ("VT003", "Tu ho so", "Cai", 2, 4_200_000), ("VT004", "May in laser", "Cai", 1, 6_500_000), ("VT005", "Giay A4", "Ram", 50, 65_000),]DO_RONG = 78def in_duong_ke(): print("-" * DO_RONG)# ---------- In bảng ----------print("=" * DO_RONG)print("BANG KE HANG HOA TON KHO".center(DO_RONG))print("=" * DO_RONG)print(f"{'STT':<5}{'MA':<8}{'TEN HANG':<20}{'DVT':<6}{'SL':>6}{'DON GIA':>14}{'THANH TIEN':>17}")in_duong_ke()tong_gia_tri = 0for stt, (ma, ten, dvt, sl, gia) in enumerate(hang_hoa, start=1): thanh_tien = sl * gia tong_gia_tri += thanh_tien print(f"{stt:<5}{ma:<8}{ten:<20}{dvt:<6}{sl:>6}{gia:>14,}{thanh_tien:>17,}")in_duong_ke()print(f"{'TONG GIA TRI TON KHO':<61}{tong_gia_tri:>17,}")print("=" * DO_RONG)# ---------- Thống kê ----------gia_tri_tung_mat = [sl * gia for _, _, _, sl, gia in hang_hoa]print()print("THONG KE:")print(f" So mat hang : {len(hang_hoa)}")print(f" Tong gia tri : {sum(gia_tri_tung_mat):,}")print(f" Gia tri lon nhat : {max(gia_tri_tung_mat):,}")print(f" Gia tri nho nhat : {min(gia_tri_tung_mat):,}")print(f" Gia tri trung binh : {sum(gia_tri_tung_mat) / len(gia_tri_tung_mat):,.0f}")# ---------- Lọc: các mặt hàng giá trị >= 5 triệu ----------print()print("CAC MAT HANG CO GIA TRI TU 5 TRIEU:")hang_lon = [(ma, ten, sl * gia) for ma, ten, _, sl, gia in hang_hoa if sl * gia >= 5_000_000]if hang_lon: for ma, ten, gt in sorted(hang_lon, key=lambda x: x[2], reverse=True): print(f" {ma} - {ten:<20}: {gt:>15,}")else: print(" (Khong co)")
12. Thực hành bắt buộc
Bài 1 — Cơ bản với list
Cho ds = [15, 8, 42, 4, 23, 16, 15, 8], in ra:
Phần tử đầu, cuối, và 3 phần tử giữa
Danh sách đảo ngược
Danh sách đã sắp xếp tăng dần (không sửa ds gốc)
Tổng, trung bình, min, max
Số lần xuất hiện của 15
Danh sách không trùng lặp (gợi ý: dùng set — xem 09 - Dict và Set)
Bài 2 — Thao tác thêm/xóa
Viết chương trình quản lý danh sách công việc (to-do list) với menu:
1. Them cong viec
2. Xoa cong viec (theo so thu tu)
3. Xem danh sach
4. Danh dau hoan thanh
0. Thoat
Bài 3 — Bẫy tham chiếu
Dự đoán kết quả trước khi chạy, giải thích tại sao: