- Hiểu cách Python khai báo biến (không có
var/let) và conventionsnake_case. - Master
intarbitrary precision — vì sao2**100chạy được mà JS thì không. - Biết khi nào dùng
float,decimal.Decimalvàfractions.Fraction. - Thành thạo
strUnicode + slicing + methods + f-string với format spec. - Hiểu
boollà subclass củaintvàNonelà singleton. - Phân biệt immutable vs mutable, ngữ nghĩa name → object của Python.
- Biết khi nào dùng
==, khi nào dùngis, khi nào dùngin. - Viết chain comparison
0 < x < 10đúng cách và bắt đầu làm quen type hints.
1. Khai báo biến — không cần var, let hay type
Python không có keyword khai báo biến. Gán giá trị là tạo biến. Không có var,
let, const; cũng không cần ghi type. Đây là phong cách "code đọc như văn bản" của Python.
x = 5
name = "An"
is_active = True
price = 19.99
print(type(x)) # <class 'int'>
print(type(name)) # <class 'str'>
print(type(is_active)) # <class 'bool'>
# Reassign sang type khác — runtime cho phép
x = "giờ tôi là chuỗi"
print(type(x)) # <class 'str'>
Dynamic: type gắn với giá trị, không gắn với biến. Biến chỉ là cái tên (label) trỏ vào object. Đổi giá trị có thể đổi luôn type.
Strong: nhưng Python không tự ý coerce type. "5" + 3 sẽ raise
TypeError — khác hẳn JavaScript trả "53". Đây là điểm an toàn của Python so với JS.
1.1. Convention đặt tên — PEP 8
| Loại | Convention | Ví dụ |
|---|---|---|
| Biến, hàm | snake_case | user_name, calculate_total() |
| Hằng số | UPPER_SNAKE | MAX_RETRY, PI |
| Class | PascalCase | UserAccount, HttpClient |
| Module / package | lowercase ngắn | os, json, requests |
| "Private" (convention) | _underscore | _internal_cache |
| Tránh va chạm với keyword | thêm _ cuối | class_, type_ |
# ✅ Đúng — snake_case
user_age = 25
first_name = "An"
def calculate_total(items): ...
# ❌ Sai — camelCase (style JS/Java)
userAge = 25
def calculateTotal(items): ...
# ✅ Hằng số — UPPER_CASE (chỉ là convention, Python KHÔNG enforce)
MAX_RETRY = 3
PI = 3.14159
DEFAULT_TIMEOUT = 30
MAX_RETRY = 3 chỉ là convention. Python không có keyword như
const (JS) hay final (Dart). Code dưới vẫn chạy:
MAX_RETRY = 3
MAX_RETRY = 99 # không lỗi! chỉ là convention
Python 3.8+ có typing.Final hint cho mypy nhưng vẫn không enforce runtime. Văn hoá Python là
"we're all consenting adults" — tin nhau, không khoá cứng.
2. int — số nguyên độ chính xác vô hạn
Đây là điểm khác biệt lớn nhất của Python so với JavaScript và Dart. Trong Python,
int không giới hạn số chữ số — nó tự động phình to (arbitrary precision) khi cần.
x = 2 ** 100
print(x)
# 1267650600228229401496703205376
print(type(x))
# <class 'int'> — vẫn là int, không cần BigInt riêng
# Tính giai thừa 100 — vẫn chính xác từng chữ số
import math
print(math.factorial(100))
# 93326215443944152681699238856266700490715968264381621468592963895217...
Đối chiếu với các ngôn ngữ khác:
| Ngôn ngữ | Số nguyên tối đa | Vượt thì sao? |
|---|---|---|
Python int | Vô hạn (chỉ giới hạn RAM) | Không overflow |
JavaScript Number | 2^53 - 1 ≈ 9 × 10¹⁵ | Mất precision (silent) |
JavaScript BigInt | Vô hạn | OK nhưng phải dùng hậu tố n |
Dart int (VM) | 64-bit signed (~9.2 × 10¹⁸) | Wrap-around (overflow ngầm) |
C/C++ int | 32-bit (~2.1 × 10⁹) | Wrap-around (Undefined Behaviour) |
Số nhỏ (vừa với 1 word CPU) Python lưu inline, tốc độ gần như C. Số lớn vượt 64-bit thì Python chuyển sang
"bignum" lưu thành mảng digit, thao tác chậm hơn ~10–100×. Trong 99% code thường ngày bạn không cảm nhận
được, nhưng nếu làm crypto / số học hạng nặng thì cân nhắc dùng numpy.int64 hoặc thư viện
chuyên biệt như gmpy2.
2.1. Toán tử số nguyên
7 + 3 # 10 — cộng
7 - 3 # 4 — trừ
7 * 3 # 21 — nhân
7 / 3 # 2.3333... — chia (LUÔN trả float, kể cả int / int)
7 // 3 # 2 — chia lấy nguyên (floor division)
7 % 3 # 1 — chia lấy dư
7 ** 3 # 343 — luỹ thừa
# Lưu ý: // và % với số âm — Python floor về phía âm vô cực
-7 // 3 # -3 (KHÔNG phải -2 như C/Java)
-7 % 3 # 2 (luôn cùng dấu với divisor)
3. float — IEEE 754 double và những cạm bẫy
float trong Python = IEEE 754 double-precision 64-bit, giống hệt JavaScript
Number và Dart double. Mọi gotcha của số thực binary đều áp dụng.
>>> 0.1 + 0.2
0.30000000000000004
>>> 0.1 + 0.2 == 0.3
False
# So sánh đúng — math.isclose
>>> import math
>>> math.isclose(0.1 + 0.2, 0.3)
True
# float đặc biệt
>>> float("inf")
inf
>>> float("nan")
nan
>>> float("nan") == float("nan")
False # NaN không bằng chính nó — IEEE 754 rule
3.1. decimal.Decimal — khi cần chính xác (tiền)
Không bao giờ tính tiền bằng float. Dùng Decimal — số thập phân chính xác từng chữ số.
from decimal import Decimal, getcontext
# Khởi tạo từ chuỗi để tránh lỗi float lúc gõ literal
price = Decimal("19.99")
tax = Decimal("0.08")
total = price * (1 + tax)
print(total) # 21.5892 — chính xác từng cent
# So với float — sai số tích luỹ
print(19.99 * 1.08) # 21.589200000000002 (!!)
# Set precision toàn cục
getcontext().prec = 10
Decimal("1") / Decimal("7") # Decimal('0.1428571429')
3.2. fractions.Fraction — phân số
from fractions import Fraction
a = Fraction(1, 3)
b = Fraction(1, 6)
print(a + b) # 1/2 — tự rút gọn
print(Fraction("0.1") + Fraction("0.2"))
# 3/10 — tránh hoàn toàn lỗi 0.30000000000000004
4. str — chuỗi Unicode mặc định
Khác Python 2 (lưu byte ASCII), Python 3 str là Unicode. Mọi ký tự — tiếng Việt có dấu, emoji, tiếng Trung — đều được hỗ trợ first-class.
s = "Xin chào, Việt Nam! 🇻🇳"
print(len(s)) # đếm code point Unicode, không đếm byte
print(type(s)) # <class 'str'>
# Indexing — như array
print(s[0]) # 'X'
print(s[-1]) # '🇻🇳' (đếm từ cuối)
# Slicing — s[start:stop:step], stop loại trừ
print(s[0:3]) # 'Xin'
print(s[:3]) # 'Xin' (start mặc định = 0)
print(s[4:]) # 'chào, Việt Nam! 🇻🇳'
print(s[::-1]) # đảo ngược chuỗi
# IMMUTABLE — không sửa được ký tự
s[0] = "Y" # TypeError: 'str' object does not support item assignment
4.1. Methods quan trọng của str
| Method | Tác dụng | Ví dụ |
|---|---|---|
upper() / lower() | Viết hoa / viết thường | "abc".upper() → 'ABC' |
strip() | Bỏ whitespace 2 đầu | " hi ".strip() → 'hi' |
split(sep) | Tách chuỗi thành list | "a,b,c".split(",") → ['a','b','c'] |
join(iterable) | Nối list thành chuỗi | "-".join(["a","b"]) → 'a-b' |
replace(old, new) | Thay thế | "abc".replace("b","X") → 'aXc' |
startswith / endswith | Check prefix / suffix | "abc".startswith("a") → True |
find / index | Tìm vị trí substring | "hello".find("l") → 2 |
count(sub) | Đếm số lần xuất hiện | "banana".count("a") → 3 |
isdigit() | Toàn ký số? | "123".isdigit() → True |
csv = " 1, 2 ,3 ,4"
nums = [int(x.strip()) for x in csv.split(",")]
print(nums) # [1, 2, 3, 4]
# Ngược lại — list → string
print(", ".join(str(n) for n in nums))
# "1, 2, 3, 4"
5. f-string — format string hiện đại
Ra đời từ Python 3.6 (PEP 498), f-string là cách format string hiện đại và nhanh nhất.
Thay thế hoàn toàn %-formatting cũ và "{}".format() trong code mới.
name = "An"
age = 25
# Cũ — % formatting (1990s)
"Tôi tên %s, %d tuổi" % (name, age)
# Cũ hơn chút — .format()
"Tôi tên {}, {} tuổi".format(name, age)
# Hiện đại — f-string (Python 3.6+)
print(f"Tôi tên {name}, {age} tuổi")
# Tôi tên An, 25 tuổi
# Expression bên trong
print(f"Năm sau: {age + 1}")
print(f"Tên hoa: {name.upper()}")
print(f"Là người lớn: {age >= 18}")
5.1. Format specification — căn lề, làm tròn, dấu phân cách
Cú pháp: {value:format_spec}. Đây là siêu năng lực của f-string.
x = 3.14159
n = 1234567
name = "An"
# Làm tròn số thập phân
print(f"{x:.2f}") # '3.14'
print(f"{x:.4f}") # '3.1416'
# Phân cách hàng nghìn
print(f"{n:,}") # '1,234,567'
print(f"{n:_}") # '1_234_567' (Python style)
# Căn lề (mặc định 10 ký tự)
print(f"{name:>10}") # ' An' (căn phải)
print(f"{name:<10}") # 'An ' (căn trái)
print(f"{name:^10}") # ' An ' (căn giữa)
print(f"{name:*^10}") # '****An****' (fill bằng *)
# Phần trăm
ratio = 0.7523
print(f"{ratio:.1%}") # '75.2%'
# Hex / binary / octal
print(f"{255:x}") # 'ff'
print(f"{255:#x}") # '0xff'
print(f"{10:b}") # '1010'
# Debug self-documenting (Python 3.8+) — in cả tên biến
print(f"{x=}") # 'x=3.14159'
Trước 3.12, f-string có nhiều giới hạn (không nest cùng loại quote, không xuống dòng trong biểu thức, không dùng backslash). Từ Python 3.12 (PEP 701), f-string trở thành một biểu thức Python đầy đủ:
# Trước 3.12 — lỗi syntax
print(f"{"hello".upper()}")
# 3.12+ — OK, nest quote thoải mái, xuống dòng cũng được
print(f"{
"hello".upper()
if shout else "hello"
}")
6. bool — subclass của int
Đây là chi tiết "kì cục nhưng tiện" của Python: True và False không chỉ là boolean —
chúng thực sự là số. bool là một subclass của int.
>>> isinstance(True, int)
True
>>> True == 1
True
>>> False == 0
True
>>> True + True
2
>>> True * 10
10
>>> sum([True, True, False, True])
3 # đếm True trong list — idiom Pythonic
numbers = [1, 2, 3, 4, 5, 6]
even_count = sum(1 for n in numbers if n % 2 == 0)
# Hoặc gọn hơn nữa, tận dụng True == 1:
even_count = sum(n % 2 == 0 for n in numbers)
print(even_count) # 3
6.1. Truthy / Falsy
Khi cần boolean (vd: if, while, not), Python xem các giá trị sau là falsy:
FalseNone0,0.0,0j(complex)""(empty string)[],(),{},set()(empty collection)- Object có method
__bool__trảFalse, hoặc__len__trả0
Mọi giá trị khác đều truthy. Khác JavaScript ở chỗ Python coi collection rỗng là falsy:
>>> bool([]) # False ✅ (JS: true)
False
>>> bool({}) # False ✅ (JS: true)
False
>>> bool("0") # True (string khác empty)
True
# Pythonic check rỗng — không cần len()
items = []
if not items:
print("không có gì")
7. None — singleton "không có giá trị"
None tương đương null của các ngôn ngữ khác. Nhưng có 2 điểm đặc biệt:
- Singleton — chỉ có duy nhất 1 object
Nonetrong toàn bộ runtime. - Type là
NoneType— không thể subclass, không thể tạo thêm instance.
>>> type(None)
<class 'NoneType'>
>>> a = None
>>> b = None
>>> a is b
True # cả 2 đều trỏ vào CÙNG MỘT object None duy nhất
>>> id(a) == id(b)
True
is None, không dùng == None
Cả 2 đều "work" cho trường hợp thông thường, nhưng luôn dùng is None vì:
isnhanh hơn (chỉ so sánh con trỏ).==gọi__eq__— một object lập dị có thể override đểobj == NonetrảTruengay cả khi không phải None.- Lint tool (ruff, pylint, flake8) sẽ cảnh báo nếu thấy
== None.
# ❌ Tránh
if x == None: ...
# ✅ Đúng
if x is None: ...
if x is not None: ...
8. Immutable vs Mutable — chia 2 phe của vũ trụ Python
Mọi object trong Python rơi vào 1 trong 2 phe: immutable (không sửa được) hoặc mutable (sửa được tại chỗ). Phân biệt được hai phe này là chìa khoá để hiểu mọi bug "reference" sau này.
| Immutable | Mutable |
|---|---|
int, float, complexstrtuplefrozensetbytesbool, None
|
listdictsetbytearrayMọi class do người dùng định nghĩa (mặc định) |
# str — immutable
s = "hello"
s[0] = "H" # TypeError
s = s.replace("h", "H") # tạo string MỚI, gán lại tên s
# list — mutable
xs = [1, 2, 3]
xs[0] = 99 # OK — sửa tại chỗ
xs.append(4) # OK — sửa tại chỗ
print(xs) # [99, 2, 3, 4]
8.1. Hệ quả: chỉ immutable mới làm key của dict / phần tử của set
Vì hash phải ổn định, mutable không có __hash__.
>>> hash("hello") # OK
-4378789663243879052
>>> hash((1, 2, 3)) # tuple immutable → OK
2528502973977326415
>>> hash([1, 2, 3]) # list mutable → lỗi
TypeError: unhashable type: 'list'
# Áp dụng — dùng tuple làm key
locations = {
(21.0285, 105.8542): "Hà Nội",
(10.7769, 106.7009): "TP.HCM",
}
9. Reference semantics — "tên trỏ vào object"
Đây là model quan trọng nhất cần nắm. Trong Python, biến không "chứa" giá trị — biến là một
cái nhãn trỏ vào object trên heap. Gán b = a không copy; nó chỉ làm b trỏ
vào cùng object như a.
a = [1, 2, 3]
b = a # b trỏ CÙNG list với a, không phải copy!
b.append(4)
print(a) # [1, 2, 3, 4] ← a cũng đổi!!
print(a is b) # True (cùng object)
Hãy nghĩ biến như sticky note dán lên object. b = a = dán thêm 1 sticky note b
lên đúng object a đang trỏ. Cả 2 note đều "thấy" cùng một object — sửa qua note nào cũng đổi.
Với immutable, "sửa" không đổi object — nó tạo object mới và dán lại note. Đó là lý do
x = 5; y = x; x = 6 không ảnh hưởng y.
9.1. copy.copy vs copy.deepcopy
import copy
original = [[1, 2], [3, 4]]
# Shallow — copy top-level, nhưng nested object vẫn share
shallow = copy.copy(original) # hoặc original[:] hoặc list(original)
shallow[0].append(99)
print(original) # [[1, 2, 99], [3, 4]] ← bị ảnh hưởng!
# Deep — copy cả nested
deep = copy.deepcopy(original)
deep[0].append(777)
print(original) # [[1, 2, 99], [3, 4]] ← không đổi
print(deep) # [[1, 2, 99, 777], [3, 4]]
10. == vs is — bằng giá trị vs cùng object
Hai toán tử dễ nhầm. Quy tắc:
| Toán tử | Hỏi cái gì? | Cơ chế |
|---|---|---|
== | Giá trị có bằng nhau? | Gọi __eq__, có thể coerce nhẹ |
is | Có phải cùng 1 object? | So sánh id() — không gọi method |
>>> 5 == 5.0
True # giá trị toán học bằng nhau
>>> 5 is 5.0
False # int và float là 2 object khác nhau
>>> a = [1, 2]
>>> b = [1, 2]
>>> a == b
True # cùng nội dung
>>> a is b
False # nhưng là 2 list khác nhau trong RAM
>>> c = a
>>> c is a
True # giờ thì cùng object
is vớiNone:if x is None:True/Falsetrong context cần phân biệt (hiếm)- Sentinel object:
_SENTINEL = object()rồiif x is _SENTINEL:
Mọi trường hợp khác — dùng ==.
11. Toán tử in — kiểm tra thành phần
Một trong những toán tử "đáng tự hào" của Python — đọc gần như tiếng Anh tự nhiên.
in hoạt động trên mọi container/iterable.
# Substring trong string
"chao" in "xin chao" # True
"X" in "xin chao" # False (case-sensitive)
# Phần tử trong list / tuple
3 in [1, 2, 3] # True
9 in (1, 2, 3) # False
# Key trong dict (KHÔNG phải value)
d = {"a": 1, "b": 2}
"a" in d # True
1 in d # False (1 là value, không phải key)
1 in d.values() # True — check value
# Phần tử trong set
5 in {1, 3, 5} # True
# Phủ định — not in
"z" not in "abc" # True
11.1. Độ phức tạp — list vs set/dict
| Container | Độ phức tạp x in container | Lý do |
|---|---|---|
list, tuple | O(n) | Quét tuyến tính từng phần tử |
str | O(n × m) | Substring search |
set, frozenset | O(1) trung bình | Hash table lookup |
dict | O(1) trung bình | Hash table lookup theo key |
Nếu bạn check membership nhiều lần trên cùng một collection, đổi sang set sẽ nhanh hơn O(n) → O(1).
Với 1 triệu phần tử, đây là sự khác biệt giữa "chạy 5 phút" và "chạy 50ms".
banned_ids = [101, 202, 303, # ... 1M phần tử]
if user.id in banned_ids: # O(n) — chậm
...
banned_ids = set(banned_ids)
if user.id in banned_ids: # O(1) — nhanh
...
12. Chain comparison — đẹp hơn JS/Dart
Python cho phép viết toán tử so sánh liên tiếp như viết bất đẳng thức trong toán học. Đây là điểm Python đẹp hơn hẳn JavaScript/Dart.
x = 7
# Cách Pythonic — đọc như toán
if 0 < x < 10:
print("x trong khoảng (0, 10)")
# Tương đương với:
if 0 < x and x < 10:
...
# NHƯNG khác ở chỗ x chỉ được evaluate 1 LẦN
# Quan trọng nếu x là expression đắt như f() hoặc có side effect
if 0 < expensive_call() < 100: # gọi 1 lần
...
# Có thể chain nhiều hơn 2
if 0 <= score <= 100:
grade = "valid"
# Trộn nhiều toán tử cũng OK
a, b, c = 1, 2, 3
print(a < b < c) # True
print(a < b == 2) # True (a<b AND b==2)
print(a == 1 < 10 > 3) # True
Chain quá phức tạp khó đọc:
# Khó đọc — đừng chain chain kiểu này
if a < b < c < d < e < f: ...
# Dễ đọc hơn — tách
if a < b and b < c < d and d < e < f: ...
13. Type hints — gợi ý kiểu (không bắt buộc)
Từ Python 3.5 (PEP 484), Python hỗ trợ type hints — gợi ý kiểu cho biến và function. Điểm
quan trọng: runtime KHÔNG enforce. Type hints chỉ giúp IDE, linter, và type checker như
mypy/pyright bắt lỗi trước khi chạy.
# Biến với hint
name: str = "An"
age: int = 25
prices: list[float] = [19.99, 29.99]
# Function với hint
def greet(name: str, age: int) -> str:
return f"Xin chào {name}, {age} tuổi"
# Type không bị enforce ở runtime
age: int = "hai-mươi-lăm" # KHÔNG lỗi! chạy bình thường
print(age) # "hai-mươi-lăm"
# Nhưng mypy/pyright sẽ bắt:
# error: Incompatible types in assignment (str, expected int)
- Code thư viện public, code có nhiều người dùng: luôn hint.
- Script một lần dùng: tuỳ thích.
- Đặt tool:
mypy --stricthoặcpyrighttrong CI để catch sai sót. - Chương 9 sẽ đào sâu:
Optional,Union,Generic,TypedDict,Protocol.
Bài tập
Bài 1 — Format tiền theo locale
Viết hàm format_money(amount: float, currency: str) -> str trả về chuỗi tiền tệ theo
định dạng locale. Hỗ trợ ít nhất 2 ký hiệu:
"USD"→ định dạng kiểu Mỹ:$1,234.56"EUR"→ định dạng kiểu Châu Âu:1.234,56 €
Test:
format_money(1234.56, "USD") # "$1,234.56"
format_money(1234.56, "EUR") # "1.234,56 €"
format_money(0.5, "USD") # "$0.50"
Yêu cầu: dùng f-string với format spec, không dùng thư viện ngoài.
Đáp án mẫu
def format_money(amount: float, currency: str) -> str:
if currency == "USD":
return f"${amount:,.2f}"
elif currency == "EUR":
# Trick: format kiểu US trước, rồi swap , ↔ .
us = f"{amount:,.2f}"
eu = us.replace(",", "X").replace(".", ",").replace("X", ".")
return f"{eu} €"
else:
raise ValueError(f"Unsupported currency: {currency}")
Format spec :,.2f tách:
,— dùng dấu phẩy phân cách hàng nghìn.2f— 2 chữ số sau dấu thập phân
Bài 2 — Mutable default trap
Đọc code và đoán output trước khi chạy:
def add_item(item, items=[]):
items.append(item)
return items
print(add_item("a"))
print(add_item("b"))
print(add_item("c"))
Sau đó: giải thích vì sao có bug, và sửa lại để hàm hoạt động đúng (mỗi lần gọi không tham số → list rỗng mới).
Đáp án + fix
Output thực tế:
['a']
['a', 'b']
['a', 'b', 'c']
Lý do: default argument được evaluate một lần duy nhất khi function được
định nghĩa, không phải mỗi lần gọi. Cái [] trở thành một list shared cho mọi lần gọi
không pass items.
Fix idiomatic:
def add_item(item, items=None):
if items is None:
items = []
items.append(item)
return items
Dùng None làm sentinel — immutable singleton, không bị aliasing. Đây là pattern Python
chuẩn cho mutable default. Ruff rule B006 sẽ cảnh báo bug này.
Bài 3 — Integer caching
Đoán output (mà không chạy):
a = 256
b = 256
print(a is b) # ?
a = 257
b = 257
print(a is b) # ?
# Trong file .py (không phải REPL)
def test():
a = 257
b = 257
return a is b
print(test())
Giải thích kết quả.
Đáp án
Output:
True
False # (REPL) hoặc True (file đơn lẻ — tuỳ tối ưu peephole)
True # trong function, compiler có thể intern
Lý do: CPython cache (intern) sẵn các số nguyên trong khoảng [-5, 256] —
bất cứ int nào trong range này đều trỏ vào cùng object. 257 trở lên thì
không, mỗi lần là object mới.
Bài học: is dùng để check identity, không dùng để check
value. Detail này là implementation-specific của CPython và có thể đổi giữa các phiên bản.
Luôn dùng == để so sánh giá trị, is chỉ với None / sentinel.
Bài 4 — is_palindrome
Viết hàm is_palindrome(s: str) -> bool trả True nếu chuỗi đối xứng,
bỏ qua chữ hoa/thường và ký tự không phải chữ-số.
Test cases:
is_palindrome("radar") # True
is_palindrome("A man a plan a canal Panama") # True
is_palindrome("hello") # False
is_palindrome("Race car!") # True
is_palindrome("") # True (chuỗi rỗng)
Đáp án + idiomatic
def is_palindrome(s: str) -> bool:
cleaned = [c.lower() for c in s if c.isalnum()]
return cleaned == cleaned[::-1]
Giải thích:
c.isalnum()— chỉ giữ chữ và số, bỏ space và dấu câu.c.lower()— normalize chữ hoa/thường.cleaned[::-1]— slice với step=-1 = đảo ngược list.==trên list so sánh element-wise.
Phiên bản tiết kiệm memory hơn (không tạo list đảo):
def is_palindrome(s: str) -> bool:
cleaned = [c.lower() for c in s if c.isalnum()]
i, j = 0, len(cleaned) - 1
while i < j:
if cleaned[i] != cleaned[j]:
return False
i += 1
j -= 1
return True
Bài 5 — compare với chain comparison
Viết hàm compare(a, b) -> int trả:
-1nếua < b0nếua == b1nếua > b
Yêu cầu: viết 1 dòng duy nhất (tận dụng chain comparison + bool == int).
compare(3, 5) # -1
compare(5, 5) # 0
compare(9, 2) # 1
Đáp án
def compare(a, b) -> int:
return (a > b) - (a < b)
Giải thích:
a > btrảTrue(=1) hoặcFalse(=0).a < btrảTruehoặcFalse.- Trừ 2 boolean: kết quả là 1, 0, hoặc -1.
Đây là idiom kinh điển trước Python 3 — module functools còn có
cmp_to_key để bridge với key-based sort. Cũng minh hoạ rõ "bool là int".
Quiz
True + True trong Python trả về gì?
Xem đáp án
2. bool là subclass của int, nên True == 1 và
False == 0. True + True = 1 + 1 = 2. Loại trả về là
int (không phải bool nữa). Đây là idiom phổ biến để đếm số phần tử thoả điều
kiện trong list.
[] == [] và [] is [] trả gì?
Xem đáp án
[] == [] → True (cùng nội dung, cùng rỗng).
[] is [] → False — mỗi [] tạo một list MỚI trong RAM, hai object
khác nhau dù trông giống.
Đây là minh hoạ kinh điển: == hỏi "giá trị có bằng?", is hỏi "có phải cùng 1
object?". Chỉ singleton như None, True, False mới đáng so sánh
bằng is.
"abc" * 3 trả gì? Vì sao?
Xem đáp án
'abcabcabc'. Python overload toán tử * cho string — nó lặp lại chuỗi
n lần. Cũng áp dụng cho list: [0] * 5 → [0, 0, 0, 0, 0].
Cẩn thận với [[]] * 3 = [[], [], []] — cả 3 list rỗng đều là cùng object
(vấn đề aliasing). Dùng [[] for _ in range(3)] để có 3 list riêng biệt.
Vì sao x = 0.1 + 0.2; x == 0.3 trả False?
Xem đáp án
Vì float dùng IEEE 754 — không biểu diễn chính xác được 0.1 hay 0.2
trong binary (cả 2 là phân số tuần hoàn vô hạn). Cộng 2 số xấp xỉ → kết quả là 0.30000000000000004,
khác 0.3 (cũng xấp xỉ).
Cách so sánh đúng:
math.isclose(0.1 + 0.2, 0.3)— dùng tolerance hợp lý.Decimal("0.1") + Decimal("0.2") == Decimal("0.3")— chính xác từng chữ số.
len("café") trả gì trong Python 3?
Xem đáp án
4. Python 3 str là Unicode, len() đếm code point —
c, a, f, é là 4 ký tự.
Trong Python 2, "café" mặc định là bytes UTF-8, len trả
5 (ký tự é chiếm 2 byte). Đây là một trong những lý do lớn nhất khiến cộng đồng
migrate sang Python 3.
Gotcha: với emoji hợp thành như "🇻🇳" (flag = 2 regional indicator), len có thể
trả 2 — vì đếm theo code point, không phải "ký tự cảm nhận". Đếm grapheme cluster cần thư viện
grapheme.
Biểu thức 0 < x < 10 < y có hợp lệ trong Python không?
Xem đáp án
Có. Python hỗ trợ chain comparison với nhiều mắt xích. Biểu thức tương đương
(0 < x) and (x < 10) and (10 < y), nhưng các giá trị giữa (x,
10) chỉ được evaluate 1 lần — quan trọng nếu chúng là function call có
side effect.
Đây là điểm Python đẹp hơn JavaScript/Dart — viết bất đẳng thức như trong toán học. Tuy nhiên không nên chain quá 3 mắt vì khó đọc.
Code x: int = "5" ở runtime Python có gặp lỗi không?
Xem đáp án
Không có lỗi runtime! Type hints (: int) không được Python interpreter
enforce. Code chạy bình thường, x mang giá trị chuỗi "5".
Để bắt được lỗi loại này, cần chạy type checker tĩnh như mypy hoặc
pyright. Lệnh mypy script.py sẽ báo:
error: Incompatible types in assignment
(expression has type "str", variable has type "int")
Triết lý Python: type hints là contract giữa dev với dev, không phải bộ phận an toàn runtime.
Chương 9 đào sâu typing với Optional, Union, Generic,
TypedDict...
Tổng kết
Qua chương 2, bạn đã nắm:
- Khai báo biến không cần keyword, dùng
snake_case, hằng số là conventionUPPER_CASE. intđộ chính xác vô hạn — siêu năng lực Python; trade-off chậm hơn khi số rất lớn.floatIEEE 754 — biết khi nào chuyển sangDecimal(tiền) hoặcFraction(phân số chính xác).strUnicode mặc định, immutable, slicing mạnh, hàng tá methods.- f-string với format spec đầy đủ — vũ khí chính để hiển thị data.
boollà subclassint,Nonelà singleton — luôn checkis None.- Immutable vs mutable — chia 2 phe, ảnh hưởng pass-by-reference, hashable, dict key.
- Reference semantics — biến là tên trỏ object; copy.copy vs copy.deepcopy khi cần.
==vsis— luôn dùng==,ischỉ với None/sentinel.inđa năng — O(1) trên set/dict, O(n) trên list.- Chain comparison — viết bất đẳng thức như toán học.
- Type hints — không enforce runtime, nhưng cần thiết cho code chuyên nghiệp.
Kết nối
- Chương 3 (Data Structures) — đào sâu
list,tuple,dict,set+ comprehensions. - Chương 9 (Type Hints) — từ hint cơ bản tới
Optional,Generic,Protocol, mypy strict mode. - JavaScript Chương 2 — đối chiếu type coercion JS (kẹt rất nhiều "wat") vs strong typing Python.
- Dart Chương 2 — đối chiếu sound null safety Dart vs
Noneconvention của Python.