- Viết
if/elif/elseđúng PEP 8 indent, hiểu Python không có ternary?:mà dùngx if cond else y. - Master
match/case— structural pattern matching, hơn hẳn switch của ngôn ngữ khác: match shape data, sequence, mapping, class. - Hiểu
for x in iterabledùng được cho list/tuple/string/dict/file/generator; dùngenumerate,zipđúng cách. - Biết khi nào dùng
whilethay vìfor;break/continueđể giảm nesting. - Hiểu else clause của loop — đặc thù Python, chạy khi loop xong không bị
break. - Viết function với
defđúng convention: type hints + docstring + return. - Phân biệt default args, keyword args,
*args,**kwargs, positional-only/, keyword-only*. - Master LEGB scope,
globalvsnonlocal, closure pattern. - Hiểu lambda chỉ là 1 expression — dùng đúng chỗ (sorted key, filter), không lạm dụng.
- Định nghĩa chính xác pass-by-object-reference — Python không phải pass-by-value, cũng không phải pass-by-reference.
1. if / elif / else
Python không dùng dấu ngoặc nhọn {} để gom khối — mà dùng indent.
PEP 8 quy ước 4 space (không phải tab). Indent quyết định khối nào thuộc về if.
Sau điều kiện là dấu hai chấm :, không có then.
def classify(score: int) -> str:
if score >= 90:
return 'A'
elif score >= 80:
return 'B'
elif score >= 70:
return 'C'
else:
return 'F'
# Lưu ý: `elif` viết liền — KHÔNG có `else if` như JS/C.
# Đây là từ khóa riêng, tiết kiệm 1 cấp indent.
1.1. Không có ?: — dùng conditional expression
Python không có toán tử ternary cond ? a : b. Thay vào đó là conditional expression
với cú pháp tự nhiên hơn: a if cond else b — đọc như câu tiếng Anh.
age = 20
status = 'adult' if age >= 18 else 'minor'
# status == 'adult'
# Nested (đọc khó — nên tránh):
grade = 'A' if score >= 90 else 'B' if score >= 80 else 'C'
# Khi tránh: chuyển sang if/elif/else 4 dòng cho dễ đọc.
1.2. Truthy / Falsy trong điều kiện
Như đã nói Chương 2: if x: không cần so sánh tường minh với None hay rỗng.
Python tự coi các giá trị sau là falsy: None, False, số 0
(0, 0.0, 0j), chuỗi/list/tuple/dict/set rỗng. Còn lại là truthy.
# Anti-pattern (Java-ish):
if len(items) > 0 and items != None:
...
# Pythonic:
if items:
...
# Cảnh báo: đừng dùng truthy khi cần phân biệt None với 0/rỗng!
def connect(timeout=None):
if timeout: # BUG: timeout=0 cũng bị bỏ qua
...
if timeout is not None: # ĐÚNG: chỉ check None
...
if x: và if x is not None: không tương đương khi x có thể là
0, 0.0, "", []. Trong API có argument optional,
luôn kiểm tra is None để tránh confuse "không truyền" với "truyền giá trị falsy".
2. match / case — Structural pattern matching (3.10+)
Trước Python 3.10, không có cú pháp switch. Từ 3.10, PEP 634 mang đến
structural pattern matching — mạnh hơn switch của Java/C/JS rất nhiều: không chỉ so sánh
giá trị mà còn match cấu trúc dữ liệu và bind biến.
def http_status(code: int) -> str:
match code:
case 200:
return 'OK'
case 301 | 302: # OR pattern
return 'Redirect'
case 404:
return 'Not Found'
case _: # wildcard — như `default`
return 'Unknown'
2.1. Match sequence (list / tuple)
Đây là điểm khác biệt lớn với switch: match có thể nhìn vào cấu trúc
của list/tuple. Dùng *rest để gom phần còn lại.
def describe_list(lst: list) -> str:
match lst:
case []:
return 'rỗng'
case [x]:
return f'một phần tử: {x}'
case [x, y]:
return f'hai phần tử: {x}, {y}'
case [1, 2, *rest]: # bắt đầu bằng 1, 2 — phần dư vào rest
return f'bắt đầu 1,2 — rest = {rest}'
case [first, *_, last]: # _ = bỏ qua
return f'đầu = {first}, cuối = {last}'
describe_list([]) # 'rỗng'
describe_list([1, 2, 3, 4]) # 'bắt đầu 1,2 — rest = [3, 4]'
describe_list([5, 6, 7, 8]) # 'đầu = 5, cuối = 8'
2.2. Match mapping (dict)
Match key trong dict, bind value vào biến. Pattern chỉ cần subset của dict.
def handle_event(event: dict) -> str:
match event:
case {'type': 'click', 'x': x, 'y': y}:
return f'click tại ({x}, {y})'
case {'type': 'keypress', 'key': key}:
return f'phím {key}'
case {'type': t}:
return f'event chưa xử lý: {t}'
case _:
return 'không phải event dict'
handle_event({'type': 'click', 'x': 10, 'y': 20, 'meta': True})
# 'click tại (10, 20)' — pattern match dù dict có thêm key 'meta'
2.3. Match class (dataclass / instance)
Mạnh nhất: match shape của instance. Dùng tên class + đặt vị trí/keyword pattern lên field.
from dataclasses import dataclass
@dataclass
class Point:
x: int
y: int
def where(p: Point) -> str:
match p:
case Point(x=0, y=0):
return 'gốc tọa độ'
case Point(x=0, y=y_val): # bind y vào y_val
return f'trục Y, y={y_val}'
case Point(x=x_val, y=0):
return f'trục X, x={x_val}'
case Point():
return f'điểm thường: ({p.x}, {p.y})'
where(Point(0, 5)) # 'trục Y, y=5'
2.4. Guard với if
Thêm điều kiện sau pattern bằng if — gọi là guard.
match point:
case Point(x=x, y=y) if x == y:
print('trên đường y=x')
case Point(x=x, y=y) if x > 0 and y > 0:
print('góc phần tư I')
case _:
print('khác')
match khác switch như thế nào?
switch trong C/Java/JS chỉ so sánh bằng giá trị (thường là số nguyên hay chuỗi).
match Python so sánh cấu trúc: shape của list, key của dict, class + field, hợp pattern
(|), guard. Đặc biệt — bind biến ngay trong pattern. Hệ quả: thay được rất
nhiều chuỗi if isinstance(x, ...): ....
3. Vòng lặp for
for trong Python không phải for (i=0; i<n; i++) như C/Java.
Mà là for x in iterable — duyệt qua mọi iterable: list, tuple, string, dict, set,
file, generator, range...
# Duyệt list
for name in ['An', 'Bình', 'Cường']:
print(name)
# Duyệt string — từng ký tự
for ch in 'python':
print(ch)
# Duyệt range — tương đương for(i=0; i<5; i++)
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(1, 10, 2): # start=1, stop=10, step=2 → 1, 3, 5, 7, 9
print(i)
# Duyệt dict — KEY (mặc định)
prices = {'apple': 10, 'banana': 5}
for k in prices: # 'apple', 'banana'
print(k)
for k, v in prices.items(): # key + value
print(k, v)
# Duyệt file — từng dòng (không cần load toàn bộ file)
with open('data.txt') as f:
for line in f:
print(line.strip())
3.1. enumerate và zip
Hai built-in cực kỳ thông dụng cho loop pattern. enumerate ghép index + value;
zip ghép song song nhiều iterable.
# Anti-pattern (C-style):
names = ['An', 'Bình', 'Cường']
for i in range(len(names)):
print(i, names[i])
# Pythonic:
for i, name in enumerate(names):
print(i, name)
# 0 An
# 1 Bình
# 2 Cường
# enumerate có start tùy chỉnh:
for i, name in enumerate(names, start=1):
print(i, name)
# 1 An
# 2 Bình
# 3 Cường
# zip — duyệt song song
ages = [22, 25, 28]
for name, age in zip(names, ages):
print(name, age)
# An 22
# Bình 25
# Cường 28
# zip dừng ở iterable ngắn nhất.
# Python 3.10+ thêm zip(..., strict=True) — raise nếu độ dài khác nhau.
for a, b in zip([1,2,3], ['a','b'], strict=True):
print(a, b)
# ValueError: zip() argument 2 is shorter than argument 1
4. Vòng lặp while
while cond: chạy đến khi cond falsy. Trong Python, while
ít dùng hơn for nhiều — vì hầu hết bài toán đều có iterable phù hợp. while
chủ yếu cho: chờ user input, đọc network/stream chưa biết độ dài, polling.
# Đọc input đến khi nhập 'quit'
while True:
cmd = input('> ')
if cmd == 'quit':
break
process(cmd)
# Newton-Raphson — lặp đến khi hội tụ
def sqrt(n: float, eps: float=1e-10) -> float:
x = n
while abs(x*x - n) > eps:
x = (x + n/x) / 2
return x
# Walrus := (3.8+) cho while-loop với phép gán inline
while (line := f.readline()):
print(line)
# Đọc đến khi readline() trả chuỗi rỗng (EOF) — falsy → dừng.
5. break, continue
Như mọi ngôn ngữ: break thoát loop ngay, continue bỏ qua phần còn lại của iteration
hiện tại. Pattern hay dùng: early continue / early break để giảm nesting.
# Anti-pattern — nested sâu
for user in users:
if user.is_active:
if user.age >= 18:
if user.country == 'VN':
send_email(user)
# Pythonic — early continue (flat is better than nested):
for user in users:
if not user.is_active:
continue
if user.age < 18:
continue
if user.country != 'VN':
continue
send_email(user)
6. else clause của loop — đặc thù Python
Python cho phép gắn else vào for và while. Quy tắc:
else chạy khi loop kết thúc tự nhiên — tức là không bị break.
Cú pháp này gây bối rối với người mới (lúc đầu tưởng "else chạy nếu loop không vào lần nào") nên Raymond
Hettinger đề xuất tên hay hơn: nobreak. Tên đó rõ hơn nhiều.
# Tìm số nguyên tố
def is_prime(n: int) -> bool:
if n < 2:
return False
for i in range(2, int(n**0.5) + 1):
if n % i == 0:
return False
return True
# Viết lại dùng for-else:
def find_divisor(n: int) -> int | None:
for i in range(2, n):
if n % i == 0:
print(f'tìm thấy {i}')
return i
else:
print('không có divisor (prime)')
return None
# Quan sát hành vi:
for x in []:
print(x)
else:
print('done')
# In: 'done' — loop xong (không vào lần nào), không break.
for x in [1, 2, 3]:
if x == 2:
break
else:
print('done')
# KHÔNG in gì — loop bị break.
Pattern điển hình: tìm phần tử trong iterable — nếu không tìm thấy thì làm gì đó. Hoặc kiểm tra
điều kiện toàn iterable thỏa. Có thể tránh hoàn toàn bằng cách dùng any(), all(),
hoặc return trong loop + xử lý sau loop. Nhưng khi đọc code Python, gặp else sau
for/while phải nhớ nghĩa "nobreak".
7. Function với def
Định nghĩa function trong Python bằng def + tên + dấu ngoặc + parameters + dấu hai chấm.
PEP 484 (3.5+) khuyến khích thêm type hints. Convention: function quan trọng có
docstring đặt ngay sau def, bao bằng """...""".
def add(a: int, b: int) -> int:
"""Trả về tổng của hai số nguyên.
Args:
a: số hạng thứ nhất.
b: số hạng thứ hai.
Returns:
Tổng a + b.
Examples:
>>> add(2, 3)
5
"""
return a + b
# Truy cập docstring qua thuộc tính __doc__
print(add.__doc__)
# help(add) cũng in ra docstring
Convention docstring phổ biến: Google style (như trên), NumPy style,
reStructuredText. Chọn 1 và nhất quán toàn project. Tool sphinx,
pdoc dựa vào docstring sinh tài liệu HTML tự động.
8. Default args và keyword args
Python cho phép đặt giá trị mặc định cho parameter. Khi gọi, có thể truyền theo vị trí (positional) hoặc theo tên (keyword).
def greet(name: str, greeting: str = 'Hi') -> str:
return f'{greeting}, {name}!'
greet('An') # 'Hi, An!'
greet('An', 'Hello') # 'Hello, An!'
greet(name='An') # 'Hi, An!' — keyword
greet(greeting='Hi', name='An') # 'Hi, An!' — keyword đảo thứ tự OK
8.1. Cái bẫy mutable default
Đã nhắc Chương 2, nhưng đây là bug tuần với người mới Python — đáng lặp lại. Default
value được tính 1 lần tại thời điểm def, sau đó chia sẻ qua các lần gọi.
def append_to(item, lst=[]): # ⚠️ default = list rỗng dùng chung!
lst.append(item)
return lst
append_to(1) # [1]
append_to(2) # [1, 2] ← tích lũy giữa các lần gọi!
append_to(3) # [1, 2, 3]
# Sửa: dùng None làm sentinel, khởi tạo bên trong function.
def append_to(item, lst=None):
if lst is None:
lst = []
lst.append(item)
return lst
append_to(1) # [1]
append_to(2) # [2] ← OK, mỗi lần gọi là list mới
Có một use case (cũ): cache cấp function. Nhưng từ functools.lru_cache (đã có sẵn,
Chương 7), không có lý do gì dùng mutable default cho cache nữa. Luôn dùng None
cho default của list/dict/set.
9. *args và **kwargs
Khi không biết trước có bao nhiêu argument, dùng *args để gom positional dư
vào tuple, và **kwargs để gom keyword dư vào dict. Tên args/kwargs
chỉ là convention — bạn có thể dùng tên khác, nhưng đừng.
def log(*args, **kwargs):
print('positional:', args) # tuple
print('keyword:', kwargs) # dict
log(1, 2, 3, name='An', age=22)
# positional: (1, 2, 3)
# keyword: {'name': 'An', 'age': 22}
# Kết hợp với normal args:
def make_request(method, url, *headers, **params):
...
make_request('GET', '/api/users', 'X-Auth', 'X-Trace', page=1, limit=10)
# method='GET', url='/api/users'
# headers=('X-Auth', 'X-Trace')
# params={'page': 1, 'limit': 10}
9.1. Unpacking — chiều ngược lại
* và ** không chỉ dùng trong định nghĩa function. Trong call site,
chúng unpack ngược lại — tách tuple/list ra positional, tách dict ra keyword.
def add(a, b, c):
return a + b + c
nums = [1, 2, 3]
add(*nums) # 6 — unpack list thành 3 positional
opts = {'a': 1, 'b': 2, 'c': 3}
add(**opts) # 6 — unpack dict thành keyword
# Pattern phổ biến: forward args qua wrapper
def debug_call(fn, *args, **kwargs):
print(f'calling {fn.__name__}({args}, {kwargs})')
return fn(*args, **kwargs)
10. Positional-only / và keyword-only *
Python 3.8+ cho phép thiết kế API rõ ràng hơn bằng 2 ký hiệu đặc biệt trong signature:
/— mọi tham số trước nó là positional-only: caller không được dùng tên.*— mọi tham số sau nó là keyword-only: caller bắt buộc gọi bằng tên.
def f(pos_only, /, both, *, kw_only):
...
f(1, 2, kw_only=3) # OK
f(1, both=2, kw_only=3) # OK
f(pos_only=1, both=2, kw_only=3)
# TypeError: f() got some positional-only arguments passed as keyword
f(1, 2, 3)
# TypeError: f() takes 2 positional arguments but 3 were given
10.1. Tại sao thiết kế API như vậy?
Positional-only — bảo vệ refactor
Nếu caller đang gọi f(name='An'), bạn muốn đổi tên parameter từ name sang
user_name sẽ break tất cả caller. Dùng / để không cho phép caller gắn với tên,
bạn có thể đổi tên tự do về sau. Built-in nhiều function dùng pattern này: len(obj, /),
str.format_map(mapping, /).
Keyword-only — chống "boolean argument hell"
Function với nhiều flag boolean dễ đọc khó: open('f.txt', True, False, None, True) — flag thứ 3
là gì? Bắt caller dùng keyword: open('f.txt', mode='r', buffering=False, encoding=None, errors='strict')
tự document. Đặt * trước các flag để ép keyword-only.
def connect(host, port, /, *, timeout=30, retries=3):
"""host, port phải positional. timeout, retries phải keyword."""
...
connect('localhost', 5432, timeout=60) # OK
connect('localhost', 5432, 60, 5)
# TypeError: connect() takes 2 positional arguments
11. Scope LEGB
Python tìm tên biến theo thứ tự LEGB:
| Cấp | Là gì | Ví dụ |
|---|---|---|
| Local | Bên trong function hiện tại | biến local của function |
| Enclosing | Function bao ngoài (nested) | biến của outer function |
| Global | Module level (file .py) | biến top-level, hằng PI = 3.14 |
| Built-in | Built-in namespace | len, print, range |
Python tìm theo thứ tự đó. Tìm thấy ở cấp nào dùng cấp đó.
x = 'global' # G
def outer():
x = 'enclosing' # E (đối với inner)
def inner():
x = 'local' # L
print(x)
inner()
print(x)
outer()
# local
# enclosing
print(x)
# global
11.1. global và nonlocal
Bên trong function, gán biến tạo ra biến local mới (đè LEGB). Nếu muốn gán biến cấp ngoài,
dùng global (cho G) hoặc nonlocal (cho E — enclosing).
count = 0
def increment_local():
count = count + 1 # UnboundLocalError
# count xuất hiện ở vế trái → Python coi là local; nhưng vế phải đọc count chưa khởi tạo.
def increment_global():
global count
count += 1
increment_global()
print(count) # 1
def outer():
x = 10
def inner():
nonlocal x # bind biến của outer, KHÔNG phải global
x += 1
inner()
print(x) # 11
global thường là code smell. Function thay đổi global biến → khó test, khó reasoning, race
condition trong multithread. Thay vào đó: pass argument vào, return value ra. Dùng class nếu cần state.
nonlocal chấp nhận hơn vì state vẫn nằm trong factory function (closure pattern).
12. Closure
Khi một function nested được trả về (hoặc lưu lại) và nó tham chiếu biến của function bao ngoài, ta có closure. Inner function "đóng gói" (close over) các biến đó — chúng sống tiếp cùng inner function, kể cả khi outer đã return.
def make_counter():
count = 0
def inc():
nonlocal count # cần để có thể GÁN lại count (binding outer)
count += 1
return count
return inc
c1 = make_counter()
c1() # 1
c1() # 2
c1() # 3
c2 = make_counter() # instance độc lập
c2() # 1, không chia sẻ với c1
Closure xem được biến nó capture qua thuộc tính __closure__. Mỗi cell trong tuple là một biến.
c1.__closure__
# (<cell at 0x...: int object at 0x...>,)
c1.__closure__[0].cell_contents
# 3 — giá trị count hiện tại
12.1. Closure capture binding, không phải value
Một bẫy quen thuộc — closure trong loop capture binding chứ không phải giá trị tại thời điểm tạo. Cách Python xử lý khác JavaScript một chút.
funcs = []
for i in range(3):
funcs.append(lambda: i)
[f() for f in funcs]
# [2, 2, 2] ← tất cả thấy `i` cuối loop
# Fix 1: capture qua default argument (eager)
funcs = []
for i in range(3):
funcs.append(lambda i=i: i)
[f() for f in funcs]
# [0, 1, 2]
# Fix 2: dùng factory function (mỗi iteration tạo binding mới)
def make(x):
return lambda: x
funcs = [make(i) for i in range(3)]
[f() for f in funcs]
# [0, 1, 2]
13. lambda — Anonymous function
lambda là cú pháp viết function ngắn gọn, không có tên. Hạn chế cứng:
chỉ chứa 1 biểu thức — không có statement, không có return (tự return giá trị
biểu thức), không gán biến (=), không if/for dạng statement.
# lambda params: expression
square = lambda x: x * x
square(5) # 25
# Tương đương:
def square(x):
return x * x
# Có thể có if/else expression (conditional expression):
abs_val = lambda x: -x if x < 0 else x
# Use case: callback ngắn cho sorted/filter/map
users = [{'name': 'An', 'age': 22}, {'name': 'Bình', 'age': 19}]
sorted(users, key=lambda u: u['age'])
# [{'name': 'Bình', 'age': 19}, {'name': 'An', 'age': 22}]
list(filter(lambda x: x % 2 == 0, range(10)))
# [0, 2, 4, 6, 8]
Chỉ khi function cực ngắn, dùng 1 lần, ngay tại chỗ. Nếu cần đặt tên cho lambda
(f = lambda x: ...), gần như luôn nên dùng def — có tên rõ ràng, có thể thêm
docstring, type hints. PEP 8 thậm chí khuyến nghị KHÔNG gán lambda vào biến:
# PEP 8 khuyến nghị TRÁNH:
add = lambda a, b: a + b
# Dùng def:
def add(a, b):
return a + b
13.1. operator module — thay lambda đơn giản
Cho các lambda dạng lambda x: x.attr, lambda x: x[k], stdlib có sẵn
operator.attrgetter, operator.itemgetter — nhanh hơn và rõ nghĩa hơn.
from operator import itemgetter, attrgetter
# Thay vì:
sorted(users, key=lambda u: u['age'])
# Dùng:
sorted(users, key=itemgetter('age'))
# itemgetter còn lấy nhiều field (trả tuple — sort theo nhiều khóa)
sorted(users, key=itemgetter('age', 'name'))
14. Pass-by-object-reference
Câu hỏi cổ điển: Python pass-by-value hay pass-by-reference? — Cả hai đều sai. Python dùng pass-by-object-reference (hay "call by sharing"). Khi gọi function, parameter nhận tham chiếu đến cùng object với argument. Hệ quả phụ thuộc mutable hay immutable (đã xem Chương 2).
def mutate(lst):
lst.append(99) # mutate IN-PLACE → caller thấy thay đổi
a = [1, 2, 3]
mutate(a)
print(a) # [1, 2, 3, 99]
def reassign(lst):
lst = [9, 9, 9] # gán lại biến LOCAL → caller KHÔNG thấy
b = [1, 2, 3]
reassign(b)
print(b) # [1, 2, 3]
def try_modify_int(n):
n += 1 # int immutable → tạo object mới, gán biến local
x = 10
try_modify_int(x)
print(x) # 10 — không đổi
Mô hình mental đúng
Hãy nghĩ: tất cả biến Python đều là label dán lên object. Truyền argument = dán thêm label cùng
object. append sửa object (tất cả label cùng thấy). = gỡ label dán sang object
khác (chỉ ảnh hưởng local label, không ảnh hưởng caller).
Bài tập
Bài 1 — FizzBuzz với match
Viết hàm fizzbuzz(n: int) -> list[str] trả về list FizzBuzz từ 1 đến n.
Quy tắc: bội của 3 → 'Fizz'; bội của 5 → 'Buzz'; bội của cả 3 và 5 →
'FizzBuzz'; còn lại → chuỗi của số đó.
Yêu cầu: dùng match/case thay vì chuỗi if/elif.
Đáp án
def fizzbuzz(n: int) -> list[str]:
result = []
for i in range(1, n + 1):
match (i % 3, i % 5):
case (0, 0):
result.append('FizzBuzz')
case (0, _):
result.append('Fizz')
case (_, 0):
result.append('Buzz')
case _:
result.append(str(i))
return result
fizzbuzz(15)
# ['1', '2', 'Fizz', '4', 'Buzz', 'Fizz', '7', '8', 'Fizz',
# 'Buzz', '11', 'Fizz', '13', '14', 'FizzBuzz']
Mẹo: pattern (0, 0) match trước (0, _) vì match đánh giá
theo thứ tự case — cụ thể trước, tổng quát sau.
Bài 2 — Decorator trace (skeleton cho Chương 7)
Viết trace(fn) trả về một function bao quanh fn: trước khi gọi, in
'→ tên(args, kwargs)'; sau khi gọi, in '← tên = result'. Áp dụng được
lên function bất kỳ thông qua cú pháp @trace.
@trace
def add(a, b):
return a + b
add(2, 3)
# → add((2, 3), {})
# ← add = 5
Đáp án
def trace(fn):
def wrapper(*args, **kwargs):
print(f'→ {fn.__name__}({args}, {kwargs})')
result = fn(*args, **kwargs)
print(f'← {fn.__name__} = {result}')
return result
return wrapper
@trace
def add(a, b):
return a + b
# `@trace` tương đương: add = trace(add)
Đây mới là skeleton. Decorator "thực chiến" cần functools.wraps(fn) để
giữ metadata (__name__, __doc__) của fn. Chương 7
sẽ đi sâu — bao gồm decorator với tham số, lớp decorator, functools.lru_cache.
Bài 3 — parse_command dùng match
Viết parse_command(parts: list[str]) -> str mô phỏng REPL nhỏ. Quy tắc:
['add', x, y]→f'thêm {x} = {y}'['del', id]→f'xóa {id}'['list']→'liệt kê'['quit']hoặc['exit']→'tạm biệt'- Khác →
'lệnh không hợp lệ'
Đáp án
def parse_command(parts: list[str]) -> str:
match parts:
case ['add', x, y]:
return f'thêm {x} = {y}'
case ['del', item_id]:
return f'xóa {item_id}'
case ['list']:
return 'liệt kê'
case ['quit'] | ['exit']:
return 'tạm biệt'
case _:
return 'lệnh không hợp lệ'
parse_command(['add', 'apple', '10']) # 'thêm apple = 10'
parse_command(['del', '42']) # 'xóa 42'
parse_command(['list']) # 'liệt kê'
parse_command(['quit']) # 'tạm biệt'
parse_command(['xyz']) # 'lệnh không hợp lệ'
Lưu ý: trong case ['add', x, y], x và y là biến mới
được bind. Nếu muốn match giá trị hằng số, dùng dot-name (case ['add', VALUE] bắt buộc
VALUE phải là tên có dot, ví dụ Cmd.ADD).
Bài 4 — apply(funcs, value) — function composition
Viết apply(funcs: list, value) áp dụng tuần tự danh sách function lên value:
kết quả của function i được dùng làm input cho function i+1. Trả về kết
quả cuối.
apply([str.upper, len], 'hello') # len('HELLO') = 5
apply([str, len], 12345) # len('12345') = 5
apply([], 42) # 42 (không function nào → trả nguyên)
Đáp án
def apply(funcs: list, value):
for fn in funcs:
value = fn(value)
return value
# Hoặc dùng functools.reduce:
from functools import reduce
def apply(funcs, value):
return reduce(lambda v, fn: fn(v), funcs, value)
Pattern này là nền tảng của pipeline trong functional programming. Không có
function composition operator (| như Elixir hay . như Haskell) trong
Python core — nhưng dễ tự viết.
Bài 5 — make_accumulator() qua closure
Viết make_accumulator() trả về một function. Mỗi lần gọi function đó với
value, cộng dồn vào tổng nội tại và trả về tổng hiện tại.
acc = make_accumulator()
acc(5) # 5
acc(10) # 15
acc(7) # 22
acc2 = make_accumulator()
acc2(1) # 1 — instance độc lập, không share với acc
Đáp án
def make_accumulator():
total = 0
def add(value):
nonlocal total
total += value
return total
return add
Tại sao cần nonlocal total? Vì total += value tương đương
total = total + value — Python sẽ coi total là local của
add, dẫn đến UnboundLocalError. nonlocal bảo: hãy bind
total ở scope enclosing (outer function).
Nếu thay bằng container mutable, không cần nonlocal:
def make_accumulator():
total = [0] # list — mutable
def add(value):
total[0] += value # mutate, không reassign biến
return total[0]
return add
Cách này hoạt động cả ở Python 2 (không có nonlocal) nhưng kém Pythonic. Ở Python 3
thuần, dùng nonlocal.
Quiz
Code sau in ra cái gì?
for x in []:
print(x)
else:
print('done')
Xem đáp án
In done. else sau for/while chạy khi loop kết
thúc tự nhiên — tức là không bị break. Ở đây iterable rỗng, loop kết
thúc ngay mà không có break, nên else chạy.
Đây là điểm gây bối rối cho người mới — nhiều người tưởng else nghĩa là "khi loop không
vào lần nào". Tên đề xuất hay hơn của Raymond Hettinger: nobreak.
Cho function dưới, gọi f() ba lần liên tiếp trả về gì?
def f(x=[]):
x.append(1)
return x
Xem đáp án
[1], [1, 1], [1, 1, 1]. Default x=[] được tính
1 lần khi def được thực thi — sau đó chia sẻ cho mọi lần
gọi không truyền x. Mỗi append mutate vào cùng list.
Fix: dùng None làm sentinel, khởi tạo list bên trong:
def f(x=None):
if x is None:
x = []
x.append(1)
return x
Với def f(*, a, b): ..., gọi f(1, 2) có hoạt động không?
Xem đáp án
Không. Dấu * đứng đầu ép a và b phải truyền
theo keyword. Gọi f(1, 2) raise:
TypeError: f() takes 0 positional arguments but 2 were given
Phải gọi f(a=1, b=2). Đây là pattern thiết kế API "keyword-only" — bắt caller dùng tên
rõ ràng, tránh truyền sai thứ tự.
Khai báo global và nonlocal bên trong function khác nhau ở điểm gì?
Xem đáp án
Cả hai cho phép gán (bind) biến ở scope ngoài bên trong function, nhưng target khác:
global x— bind biến ở scope G (module-level). Tạo biến mới ở module nếu chưa tồn tại.nonlocal x— bind biến ở scope E (enclosing), tức outer function gần nhất. Không tạo mới — nếu không có ở enclosing, raiseSyntaxError.
Hệ quả: nonlocal chỉ có nghĩa trong nested function. Ở top-level function thuần,
muốn sửa global thì phải dùng global.
Lambda có chứa statement được không (ví dụ if, for, return)?
Xem đáp án
Không. Lambda chỉ chứa 1 biểu thức (expression). Không có
return (giá trị biểu thức tự được trả), không có if/for/while
dạng statement, không gán biến (=).
Nhưng conditional expression (a if cond else b) là expression — dùng được:
abs_val = lambda x: -x if x < 0 else x
Tương tự, list/dict/generator comprehension là expression — dùng được trong lambda. Khi cần logic
phức tạp hơn, chuyển sang def.
Trong match/case, có thể bind biến ngay trong pattern không?
Xem đáp án
Có — đây chính là điểm mạnh của structural pattern matching so với
switch truyền thống.
match point:
case Point(x=0, y=y_val): # bind y > biến y_val
print(f'trục Y, y={y_val}')
case [first, *rest]: # bind first và rest
...
Cảnh báo: trong pattern, tên đơn (như y_val) luôn được hiểu là biến mới
bind, không phải so sánh giá trị. Muốn so sánh giá trị hằng, dùng dot-name (Status.OK)
hoặc literal trực tiếp (0, 'OK').
Cho def f(a, b, /, c, d, *, e): .... Hai cách gọi sau có hợp lệ không?
f(1, 2, 3, 4, e=5)f(1, b=2, c=3, d=4, e=5)
Xem đáp án
-
f(1, 2, 3, 4, e=5)— hợp lệ.a=1,b=2(positional, OK vì là positional-only);c=3,d=4(positional, được phép vì nằm giữa/và*);e=5(keyword, bắt buộc vì sau*). -
f(1, b=2, c=3, d=4, e=5)— không hợp lệ.bnằm trước/nên là positional-only — không được truyền bằng tên. Raise:TypeError: f() got some positional-only arguments passed as keyword arguments: 'b'.
Tóm tắt rule: tham số trước / chỉ positional. Tham số giữa / và *
được cả positional và keyword. Tham số sau * chỉ keyword.
Tổng kết
Sau Chương 4, bạn nên đã master:
if/elif/else: indent 4 space, không có ternary?:mà có conditional expressiona if cond else b.match/case(3.10+): structural pattern matching — sequence, mapping, class, guard, OR pattern, bind biến.for x in iterable:enumeratecho index,zipđể duyệt song song,zip(..., strict=True)kiểm tra độ dài bằng nhau.while: dùng khi không biết trước số lần lặp (input, polling, hội tụ).break/continue: early continue giảm nesting (Flat is better than nested).elseclause của loop: chạy khi khôngbreak— "nobreak".- Function với
def: type hints + docstring + return là convention chuyên nghiệp. - Default args + cái bẫy mutable default: luôn dùng
Nonelàm sentinel. *args/**kwargs: gom positional/keyword dư + unpack ngược ở call site./và*: positional-only / keyword-only cho API design.- LEGB scope: Local → Enclosing → Global → Built-in;
globalvsnonlocal. - Closure: function nhớ scope tạo ra nó; counter, accumulator, factory pattern.
- Lambda: 1 expression, dùng đúng chỗ (sorted key, filter, map); không gán vào biến.
- Pass-by-object-reference: mutate ảnh hưởng caller, reassign không.
Kết nối
- Chương 3 (Data Structures) — củng cố tuple/list/dict/set để hiểu sequence/mapping pattern trong
match. - Chương 5 (OOP in Python) — method chính là function gắn với object;
selfchỉ là quy ước, không phải keyword;@propertydùng decorator pattern. - Chương 6 (Iterators, Generators & Itertools) — generator function với
yieldlà biến thể đặc biệt của function; tận dụng triết lý "Pythonic for loop" của chương này. - Chương 7 (Decorators & Context Managers) — đi sâu decorator (đã thấy skeleton ở Bài tập 2);
functools.wraps,functools.lru_cache, decorator có tham số. - JavaScript Chương 3 (Functions, Scope & Closures) — so sánh: JS có 4 cách viết function và
this4 rule phức tạp; Python đơn giản hơn (1 cáchdef, không cóthis) nhưng cómatch/casemạnh hơn JSswitch.