Mục lục
- Mục tiêu bài học
- List comprehension là gì
- Cú pháp cơ bản
- Comprehension kèm filter (if)
- If-else trong expression
- Nested loop trong comprehension
- Set comprehension
- Dict comprehension
- Generator expression — lazy
- Khi nào dùng, khi nào không
- Performance: comprehension vs for vs map
- Use case AI / data thường gặp
- Bài tập
- Tóm tắt
Mục tiêu bài học
Sau bài này bạn sẽ:
- Viết được list comprehension cơ bản và biết khi nào nó thay thế được vòng
for+append. - Thêm điều kiện lọc (
ifsau loop) và biểu thức rẽ nhánh (if ... else ...trong expression). - Mở rộng sang set comprehension, dict comprehension, generator expression.
- Biết giới hạn: khi nào comprehension làm code khó đọc hơn for-loop thuần.
- Áp dụng vào use case AI / data: chuẩn hoá text, lọc record, tạo feature từ list dict.
Bài chạy trên Python 3.8 trở lên.
List comprehension là gì
List comprehension là cú pháp Python để tạo một list mới từ một iterable (list, tuple, range, generator...) bằng một biểu thức duy nhất, đặt trong dấu ngoặc vuông [ ].
Pattern hay gặp nhất là biến đổi từng phần tử: cho danh sách số, lấy bình phương từng số. Cách viết for-loop truyền thống:
# For loop + append
nums = [1, 2, 3, 4, 5]
squares = []
for n in nums:
squares.append(n * n)
print(squares) # [1, 4, 9, 16, 25]
Cùng kết quả, viết bằng list comprehension chỉ còn một dòng:
nums = [1, 2, 3, 4, 5]
squares = [n * n for n in nums]
print(squares) # [1, 4, 9, 16, 25]
Comprehension không phải tính năng mới — nó là cú pháp đường (syntactic sugar) cho pattern "khởi tạo list rỗng → duyệt → append". Ưu điểm: ý đồ rõ hơn vì chỉ thấy biểu thức trung tâm n * n và nguồn dữ liệu nums.
Cú pháp cơ bản
Cú pháp tối thiểu:
[expr for item in iterable]
expr— biểu thức tính cho từng phần tử (kết quả sẽ là 1 phần tử của list mới).item— biến tạm, đại diện cho từng phần tử lấy ra từiterable.iterable— nguồn duyệt: list, tuple, str, range, file, dict, set...
Vài ví dụ phổ biến:
# Chuyển chuỗi sang chữ thường
words = ["Hello", "WORLD", "Python"]
lower = [w.lower() for w in words]
# Tương đương:
# lower = []
# for w in words:
# lower.append(w.lower())
# Lấy độ dài từng từ
lengths = [len(w) for w in words] # [5, 5, 6]
# Dùng với range
cubes = [i ** 3 for i in range(5)] # [0, 1, 8, 27, 64]
# Duyệt ký tự trong chuỗi
chars = [c for c in "abc"] # ['a', 'b', 'c']
Phần tử trong iterable có thể là tuple, kết hợp với unpacking ngay trong phần for:
pairs = [(1, "a"), (2, "b"), (3, "c")]
keys = [k for k, _ in pairs] # [1, 2, 3]
labels = [v for _, v in pairs] # ['a', 'b', 'c']
Comprehension kèm filter (if)
Thêm điều kiện lọc bằng if đặt sau phần loop:
[expr for item in iterable if condition]
Chỉ những phần tử mà condition trả True mới được đưa qua expr và thêm vào list mới.
# Chỉ giữ số chẵn
nums = [1, 2, 3, 4, 5, 6, 7, 8]
evens = [n for n in nums if n % 2 == 0]
print(evens) # [2, 4, 6, 8]
# For loop tương đương:
# evens = []
# for n in nums:
# if n % 2 == 0:
# evens.append(n)
Có thể kết hợp biến đổi và lọc trong cùng một dòng:
# Bình phương các số chẵn
result = [n * n for n in nums if n % 2 == 0]
print(result) # [4, 16, 36, 64]
Cho phép nhiều điều kiện lọc bằng and / or:
scores = [55, 70, 82, 45, 91, 68]
passed = [s for s in scores if s >= 50 and s < 90]
print(passed) # [55, 70, 82, 68]
Lọc theo thuộc tính của object:
records = [
{"name": "a", "valid": True, "value": 10},
{"name": "b", "valid": False, "value": 20},
{"name": "c", "valid": True, "value": 30},
]
valid_values = [r["value"] for r in records if r["valid"]]
print(valid_values) # [10, 30]
If-else trong expression
Hai vị trí của if trong comprehension khác nhau hoàn toàn:
... if conditionở cuối — đó là filter, chỉ giữ phần tử thoả điều kiện.... if condition else ...ở đầu (trongexpr) — đó là biểu thức ternary, mọi phần tử đều giữ nhưng giá trị thay đổi theo điều kiện.
nums = [-3, -1, 0, 2, 5]
# Filter — list ngắn hơn input (chỉ giữ số dương)
positives = [n for n in nums if n > 0]
print(positives) # [2, 5]
# Ternary trong expr — list cùng độ dài input (số âm thay bằng 0)
clipped = [n if n > 0 else 0 for n in nums]
print(clipped) # [0, 0, 0, 2, 5]
Kết hợp cả hai cũng được — vừa lọc vừa biến đổi:
# Chỉ giữ số khác 0, đảo dấu nếu âm
result = [(-n if n < 0 else n) for n in nums if n != 0]
print(result) # [3, 1, 2, 5]
Pattern x if x > 0 else 0 (clip âm về 0) chính là hàm ReLU — activation function phổ biến nhất trong neural network. NumPy sẽ làm việc này nhanh hơn nhiều khi học ở module sau, nhưng comprehension là cách thuần Python để hiểu logic.
Nested loop trong comprehension
Có thể ghép nhiều for trong cùng comprehension. Thứ tự loop từ trái sang phải, lồng dần vào trong:
xs = [1, 2, 3]
ys = ['a', 'b']
pairs = [(x, y) for x in xs for y in ys]
print(pairs)
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')]
# For loop tương đương:
# pairs = []
# for x in xs: # loop ngoài
# for y in ys: # loop trong
# pairs.append((x, y))
Loop sau có thể phụ thuộc loop trước. Ví dụ làm phẳng một list-of-list:
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [n for row in matrix for n in row]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
Kết hợp với if — filter có thể đặt sau bất kỳ loop nào:
# Các cặp (x, y) với x < y
nums = [1, 2, 3]
pairs = [(x, y) for x in nums for y in nums if x < y]
print(pairs) # [(1, 2), (1, 3), (2, 3)]
Lưu ý quan trọng: nested comprehension dạng [[expr for j in ...] for i in ...] (tạo list-of-list) khác với [expr for i in ... for j in ...] (làm phẳng). Đừng nhầm:
grid = [[i * j for j in range(3)] for i in range(3)]
print(grid)
# [[0, 0, 0], [0, 1, 2], [0, 2, 4]]
Set comprehension
Đổi dấu ngoặc vuông [ ] sang dấu ngoặc nhọn { } là được set comprehension. Kết quả tự loại trùng lặp (theo nghĩa của set):
words = ["hello", "WORLD", "Hello", "python", "World"]
unique_lower = {w.lower() for w in words}
print(unique_lower) # {'hello', 'world', 'python'}
# For loop tương đương:
# unique_lower = set()
# for w in words:
# unique_lower.add(w.lower())
Dùng để rút unique values nhanh: ví dụ tập các nhãn (label) xuất hiện trong dataset:
records = [{"y": 0}, {"y": 1}, {"y": 0}, {"y": 2}, {"y": 1}]
labels = {r["y"] for r in records}
print(labels) # {0, 1, 2}
Cùng cú pháp filter và if-else như list comprehension đều áp dụng được.
Dict comprehension
Dict comprehension cũng dùng { } nhưng expression có dạng key: value:
{key_expr: value_expr for item in iterable}
words = ["python", "is", "fun"]
word_len = {w: len(w) for w in words}
print(word_len) # {'python': 6, 'is': 2, 'fun': 3}
# For loop tương đương:
# word_len = {}
# for w in words:
# word_len[w] = len(w)
Đảo key ↔ value của dict (lưu ý: chỉ đúng khi value duy nhất):
en_vi = {"one": "một", "two": "hai", "three": "ba"}
vi_en = {v: k for k, v in en_vi.items()}
print(vi_en) # {'một': 'one', 'hai': 'two', 'ba': 'three'}
Lọc dict theo điều kiện:
scores = {"alice": 85, "bob": 42, "carol": 90, "dan": 60}
passed = {name: s for name, s in scores.items() if s >= 60}
print(passed) # {'alice': 85, 'carol': 90, 'dan': 60}
Tạo lookup từ list of dict — pattern hay gặp khi xử lý dữ liệu:
users = [
{"id": 1, "name": "alice"},
{"id": 2, "name": "bob"},
{"id": 3, "name": "carol"},
]
by_id = {u["id"]: u for u in users}
print(by_id[2]) # {'id': 2, 'name': 'bob'}
Generator expression — lazy
Đổi dấu [ ] sang dấu ngoặc tròn ( ) không tạo tuple — mà tạo generator expression. Nó không sinh ra list ngay, mà chỉ sinh từng phần tử khi được hỏi (lazy evaluation).
nums = [1, 2, 3, 4, 5]
list_sq = [n * n for n in nums] # tạo list trong RAM ngay
gen_sq = (n * n for n in nums) # mới chỉ tạo "công thức"
print(list_sq) # [1, 4, 9, 16, 25]
print(gen_sq) # <generator object ...>
Generator chỉ sinh phần tử khi duyệt qua hoặc gọi next():
gen = (n * n for n in nums)
print(next(gen)) # 1
print(next(gen)) # 4
for x in gen: # tiếp tục từ 9
print(x)
Generator duyệt một lần, hết là hết — muốn duyệt lại phải tạo mới.
Ưu điểm chính là tiết kiệm RAM: với dữ liệu lớn, tạo list 100 triệu phần tử để rồi chỉ sum() là phí. Generator chỉ giữ một phần tử tại một thời điểm:
# List — tạo 100 triệu số trong RAM
total = sum([n * n for n in range(100_000_000)])
# Generator — không tạo list, chỉ duyệt
total = sum(n * n for n in range(100_000_000))
# Khi truyền trực tiếp vào hàm, có thể bỏ cặp ngoặc ngoài
Quy tắc thực dụng: nếu chỉ cần duyệt qua một lần để tổng hợp (sum, max, any, all, vòng for) — dùng generator. Nếu cần dùng list nhiều lần, indexing, slicing — tạo list.
Khi nào dùng, khi nào không
Comprehension không luôn tốt hơn for-loop. Tiêu chí quyết định là readability.
Nên dùng comprehension khi:
- Mục đích là biến đổi hoặc lọc một iterable thành list / set / dict mới.
- Biểu thức và điều kiện ngắn, một dòng đọc xong là hiểu.
- Chỉ một cấp lồng (một
for) hoặc tối đa nested loop đơn giản như flatten matrix.
Nên dùng for-loop thuần khi:
- Có side effect — in log, ghi file, cập nhật biến ngoài.
- Biểu thức phức tạp, có nhiều bước, cần biến phụ.
- Nested 2 cấp trở lên với điều kiện đan xen — comprehension sẽ thành "tường text".
- Có thể phát sinh exception cần handle bằng
try / except.
# Lạm dụng — đọc một dòng này mất tập trung
result = [x * y for x in range(10) if x % 2 == 0 for y in range(10) if y > x if (x + y) % 3 == 0]
# Rõ hơn nhiều khi tách for-loop
result = []
for x in range(10):
if x % 2 != 0:
continue
for y in range(x + 1, 10):
if (x + y) % 3 == 0:
result.append(x * y)
Nguyên tắc PEP 8 / The Zen of Python: readability counts. Ngắn không phải lúc nào cũng tốt.
Performance: comprehension vs for vs map
List comprehension thường nhanh hơn for-loop + append nhẹ — không phải vì "Python thông minh hơn", mà vì interpreter sinh bytecode tối ưu hơn cho comprehension (không phải lookup list.append mỗi vòng).
import timeit
setup = "nums = list(range(100_000))"
t_for = timeit.timeit("""
out = []
for n in nums:
out.append(n * n)
""", setup=setup, number=100)
t_lc = timeit.timeit(
"out = [n * n for n in nums]",
setup=setup, number=100,
)
print(t_for, t_lc) # t_lc thường < t_for
Tuy nhiên: comprehension không nhanh hơn các giải pháp vectorized — map() với hàm C built-in, hoặc NumPy:
import numpy as np
# Pure Python comprehension
out_lc = [n * n for n in range(1_000_000)]
# NumPy vectorized — thường nhanh gấp 10-100 lần
arr = np.arange(1_000_000)
out_np = arr * arr
Quy tắc nhớ: với data nhỏ vài nghìn phần tử, comprehension là đủ. Với array số học lớn (vector, matrix, tensor), NumPy / PyTorch vectorization sẽ nhanh hơn nhiều cấp độ vì chạy ở C / GPU. Bài về NumPy trong module sau sẽ đi sâu vào việc này.
Use case AI / data thường gặp
Một số pattern thực tế khi tiền xử lý dữ liệu:
1. Chuẩn hoá text — lowercase, strip:
raw = [" Hello ", "WORLD", " Python "]
cleaned = [w.strip().lower() for w in raw]
print(cleaned) # ['hello', 'world', 'python']
2. Lọc record không hợp lệ:
records = [
{"id": 1, "value": 10, "valid": True},
{"id": 2, "value": None, "valid": False},
{"id": 3, "value": 25, "valid": True},
]
valid = [r for r in records if r["valid"] and r["value"] is not None]
3. Trích cột từ list of dict thành feature matrix:
data = [
{"age": 25, "income": 30000, "score": 0.7},
{"age": 30, "income": 50000, "score": 0.8},
{"age": 22, "income": 28000, "score": 0.6},
]
features = ["age", "income", "score"]
X = [[row[f] for f in features] for row in data]
# [[25, 30000, 0.7], [30, 50000, 0.8], [22, 28000, 0.6]]
4. Tách token và đếm độ dài câu:
sentences = ["I love AI", "Python is great", "Hello world"]
tokens = [s.split() for s in sentences]
lengths = [len(s.split()) for s in sentences] # [3, 3, 2]
5. Mapping label sang index (cho classification):
labels = ["cat", "dog", "bird", "cat", "dog"]
classes = sorted(set(labels)) # ['bird', 'cat', 'dog']
label_to_idx = {c: i for i, c in enumerate(classes)}
y = [label_to_idx[l] for l in labels] # [1, 2, 0, 1, 2]
Đây là các thao tác cơ bản, sau này sẽ thay bằng Pandas / NumPy / scikit-learn cho khối dữ liệu lớn. Nhưng bản chất vẫn là: duyệt, biến đổi, lọc.
Bài tập
Bài 1: Bình phương số chẵn 1-20.
Dùng list comprehension tạo list các bình phương của số chẵn trong khoảng 1..20. Kết quả mong đợi: [4, 16, 36, 64, 100, 144, 196, 256, 324, 400].
Bài 2: Dict word → length.
Cho list từ words = ["python", "ai", "engineer", "data"], dùng dict comprehension tạo dict ánh xạ mỗi từ sang độ dài của nó. Kết quả mong đợi: {"python": 6, "ai": 2, "engineer": 8, "data": 4}.
Bài 3: Hàm ReLU thuần Python.
Cho list số xs = [-3, -1, 0, 2, 5, -7]. Dùng comprehension với if-else trong expression để tạo list mới: nếu số > 0 giữ nguyên, ngược lại thay bằng 0. Kết quả: [0, 0, 0, 2, 5, 0].
Bài 4: Flatten matrix.
Cho matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]], dùng comprehension chỉ giữ các số lẻ và làm phẳng thành list 1 chiều. Kết quả: [1, 3, 5, 7, 9].
Bài 5 (mở rộng): Cho list dict records, dùng dict comprehension tạo lookup name → record, sau đó dùng list comprehension lọc các record có "age" >= 25.
records = [
{"name": "alice", "age": 30},
{"name": "bob", "age": 22},
{"name": "carol", "age": 28},
]
Tóm tắt
- List comprehension
[expr for item in iterable]là cú pháp đường cho pattern duyệt + append. - Filter:
[expr for item in iterable if condition]—ifsau loop, chỉ giữ phần tử thoả. - Ternary trong expression:
[x if cond else y for ... ]— mọi phần tử đều giữ, giá trị biến đổi theo điều kiện. - Nested loop:
[expr for x in xs for y in ys]— đọc từ trái sang phải, lồng dần vào trong. - Set comprehension
{expr for ...}tự loại trùng; dict comprehension{k: v for ...}tạo dict. - Generator expression
(expr for ...)lazy, không tạo list trong RAM — phù hợp khi chỉ duyệt một lần. - Comprehension thường nhanh hơn for-append nhờ bytecode tối ưu, nhưng không nhanh hơn
map()với hàm C hoặc NumPy vectorization. - Lạm dụng nested comprehension làm code khó đọc — khi 2 cấp lồng trở lên, hãy tách for-loop.
- Use case AI / data hay gặp: chuẩn hoá text, lọc record, tạo feature matrix, mapping label → index.
Bài tiếp theo sẽ học try / except / finally — cơ chế xử lý lỗi của Python để code không sụp khi gặp tình huống bất thường.
Tài liệu tham khảo
- Python Docs - Tutorial: List Comprehensions
- Python Docs - Nested List Comprehensions
- Python Docs - Dictionaries (Dict Comprehensions)
- Python Docs - Displays for lists, sets and dictionaries
- Python Docs - Generator expressions
- PEP 202 - List Comprehensions
- PEP 274 - Dict Comprehensions
- PEP 289 - Generator Expressions
