- 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. Nhập / Xuất dữ liệu — input() và print()
Hai hàm built-in này là kênh giao tiếp cơ bản nhất giữa chương trình và người dùng. Chúng đơn giản, nhưng mỗi hàm đều có vài tham số ít người biết mà lại rất hữu dụng.
6.1. input() — đọc từ bàn phím
# input() nhận một chuỗi nhắc (prompt), in ra rồi CHỜ người dùng gõ Enter
ten = input("Tên bạn là gì? ")
print(f"Xin chào, {ten}!")
# ⚠️ ĐIỀU QUAN TRỌNG NHẤT: input() LUÔN trả về str
tuoi = input("Tuổi: ") # người dùng gõ 22
print(type(tuoi)) # <class 'str'> — KHÔNG phải int!
print(tuoi + 1) # TypeError: can only concatenate str (not "int") to str
# ✅ Phải ép kiểu tường minh
tuoi = int(input("Tuổi: "))
print(f"Sang năm bạn {tuoi + 1} tuổi")
input()# ① Luôn là str — quên ép kiểu là lỗi số một của người mới
n = input("Số: ")
if n > 10: # TypeError: '>' not supported between 'str' and 'int'
...
# ② Ép kiểu có thể ném ValueError khi người dùng gõ bậy
tuoi = int(input("Tuổi: ")) # gõ "abc" → ValueError, chương trình sập
# ③ Khoảng trắng thừa không tự bị bỏ
lua_chon = input("y/n: ") # người dùng gõ " y " (lỡ tay)
if lua_chon == "y": # False! vì chuỗi là " y "
...
# ✅ Cách viết đúng cho cả ba
lua_chon = input("y/n: ").strip().lower()
def nhap_so_nguyen(nhac: str, nho_nhat: int = 0, lon_nhat: int = 150) -> int:
"""Hỏi lại cho tới khi người dùng nhập số hợp lệ."""
while True:
thô = input(nhac).strip()
try:
n = int(thô)
except ValueError:
print(f" '{thô}' không phải số nguyên, thử lại.")
continue
if not nho_nhat <= n <= lon_nhat:
print(f" Phải trong khoảng [{nho_nhat}, {lon_nhat}], thử lại.")
continue
return n
tuoi = nhap_so_nguyen("Tuổi của bạn: ")
input() không dùng được ở mọi nơi
Nó cần một terminal có người ngồi trước. Trong test tự động, trong cron job, trong container không có
TTY, hoặc khi đầu vào đã hết, input() ném EOFError hoặc treo vô hạn. Vì vậy
chương trình thật lấy tham số qua dòng lệnh (sys.argv,
argparse) hoặc biến môi trường — input() chỉ dành cho công cụ
tương tác. Chương 14 sẽ dựng CLI đúng cách.
6.2. print() — bốn tham số ít người biết
# Chữ ký đầy đủ:
# print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
# ① Nhiều giá trị — tự chèn dấu cách giữa chúng
print("a", 1, True, None) # a 1 True None
# ② sep — đổi dấu phân cách
print("2026", "08", "14", sep="-") # 2026-08-14
print(*["a", "b", "c"], sep=", ") # a, b, c (* để bung list)
# ③ end — đổi ký tự kết thúc (mặc định xuống dòng)
print("Đang tải", end="")
for _ in range(3):
print(".", end="", flush=True) # flush: hiện ngay, không đợi đầy bộ đệm
print(" xong!") # Đang tải... xong!
# ④ file — ghi ra nơi khác thay vì màn hình
import sys
print("Lỗi: thiếu tham số", file=sys.stderr) # ra luồng lỗi chuẩn
with open("ket_qua.txt", "w", encoding="utf-8") as f:
print("Dòng 1", file=f) # ghi vào tệp, tự thêm \n
stdout và stderr — vì sao phải phân biệt
stdout là kết quả của chương trình; stderr là thông báo cho
người dùng. Tách hai luồng cho phép nối lệnh trong shell mà không bị lẫn:
$ python xu_ly.py > ket_qua.csv # chỉ kết quả vào tệp
Đang xử lý 100 dòng... # thông báo vẫn hiện trên màn hình (stderr)
$ python xu_ly.py 2> loi.log # chỉ thông báo lỗi vào tệp
Nếu bạn in thông báo tiến độ ra stdout, chúng sẽ lẫn vào tệp CSV và phá hỏng dữ liệu.
Quy tắc: dữ liệu ra stdout, mọi thứ khác ra stderr.
JS (Node): đọc bàn phím phải qua module readline và bất đồng bộ —
không có hàm chặn nào tương đương input(). console.log tự thêm xuống dòng,
không có tham số sep/end.
Dart: stdin.readLineSync() gần nhất với input(), trả
String? (có thể null). print() chỉ nhận một tham số.
Python: input() chặn và đơn giản; print() linh hoạt nhất
trong ba ngôn ngữ nhờ sep, end, file, flush.
7. 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
7.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ì")
8. 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: ...
9. 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]
9.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",
}
10. 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.
10.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]]
11. Toán tử — bảng đầy đủ
Bạn đã gặp rải rác các toán tử ở những mục trước. Mục này gom lại thành bảng tra cứu đầy đủ, kèm những điểm mà Python làm khác các ngôn ngữ khác.
11.1. Toán tử số học
| Toán tử | Tên | Ví dụ | Kết quả | Ghi chú |
|---|---|---|---|---|
+ | Cộng | 7 + 2 | 9 | Cũng nối str, list, tuple |
- | Trừ | 7 - 2 | 5 | Cũng là hiệu của set |
* | Nhân | 7 * 2 | 14 | "ab" * 3 → 'ababab' |
/ | Chia thực | 7 / 2 | 3.5 | Luôn trả float, kể cả 4 / 2 → 2.0 |
// | Chia lấy nguyên | 7 // 2 | 3 | Làm tròn xuống: -7 // 2 → -4 |
% | Chia lấy dư | 7 % 2 | 1 | Dấu theo số chia: -7 % 3 → 2 |
** | Luỹ thừa | 2 ** 10 | 1024 | Kết hợp phải sang trái: 2**3**2 = 512 |
# Python 3: / LUÔN cho float — khác Python 2, khác C/Java
print(6 / 3) # 2.0 (không phải 2)
print(6 // 3) # 2
# // làm tròn XUỐNG (floor), không phải cắt về 0
print(7 // 2) # 3
print(-7 // 2) # -4 ⚠️ JS/Java cho -3 (cắt về 0)
# % lấy dấu theo SỐ CHIA — rất tiện cho tính toán vòng tròn
print(-7 % 3) # 2 ⚠️ JS/Java cho -1
print((-1) % 7) # 6 → lùi 1 bước từ 0 trong vòng 7 phần tử
# divmod: lấy cả thương và dư trong một lần tính
print(divmod(3725, 60)) # (62, 5) → 62 phút 5 giây
# ** kết hợp phải sang trái
print(2 ** 3 ** 2) # 512 = 2**(3**2), KHÔNG phải (2**3)**2 = 64
11.2. Toán tử gán ghép
| Toán tử | Tương đương | Toán tử | Tương đương |
|---|---|---|---|
x += 1 | x = x + 1 | x &= y | x = x & y |
x -= 1 | x = x - 1 | x |= y | x = x | y |
x *= 2 | x = x * 2 | x ^= y | x = x ^ y |
x /= 2 | x = x / 2 | x <<= 1 | x = x << 1 |
x //= 2 | x = x // 2 | x >>= 1 | x = x >> 1 |
x %= 3 | x = x % 3 | x **= 2 | x = x ** 2 |
+= không phải lúc nào cũng bằng x = x + ya = [1, 2]
b = a
a += [3] # SỬA TẠI CHỖ list gốc (gọi list.__iadd__)
print(b) # [1, 2, 3] ← b thấy thay đổi!
a = [1, 2]
b = a
a = a + [3] # TẠO list MỚI rồi gán lại cho tên a
print(b) # [1, 2] ← b không đổi
# Với int/str/tuple (immutable) thì hai cách luôn cho kết quả như nhau
# vì không có gì để sửa tại chỗ.
Với đối tượng mutable, += gọi __iadd__ — sửa ngay trên object
hiện có. Với immutable, nó rơi về __add__ — tạo object mới. Đây là hệ quả
trực tiếp của mục 9 (Immutable vs Mutable) và mục 10 (Reference semantics).
11.3. Toán tử so sánh và logic
| Toán tử | Ý nghĩa | Ví dụ | Ghi chú |
|---|---|---|---|
== != | Bằng / khác giá trị | [1,2] == [1,2] → True | Gọi __eq__; xem mục 12 |
< > <= >= | So sánh thứ tự | "a" < "b" → True | Chuỗi so theo mã Unicode |
is is not | Cùng object hay không | x is None | Chỉ dùng với None/True/False/sentinel |
in not in | Thuộc về | 3 in [1,2,3] | Xem mục 13 |
and | Và | a and b | Trả a nếu a falsy, ngược lại trả b |
or | Hoặc | a or b | Trả a nếu a truthy, ngược lại trả b |
not | Phủ định | not x | Luôn trả bool |
# ⭐ and/or KHÔNG trả bool — chúng trả về MỘT TRONG HAI toán hạng
print(0 or "mặc định") # 'mặc định' (0 falsy → lấy vế phải)
print("An" or "mặc định") # 'An' (truthy → lấy luôn vế trái)
print(1 and 2) # 2
print(0 and 2) # 0
# Ứng dụng: giá trị mặc định gọn gàng
ten = nhap_vao or "Khách" # chuỗi rỗng → dùng 'Khách'
# ⚠️ Cẩn thận: 0 và "" đều falsy nên bị thay mất
so_luong = nhap_so or 10 # nhập 0 → thành 10! Có thể sai ý định.
so_luong = nhap_so if nhap_so is not None else 10 # ✅ chính xác
# Đoản mạch (short-circuit): vế phải KHÔNG chạy nếu không cần
if ds and ds[0] == "x": # ds rỗng → không đụng ds[0] → không IndexError
...
11.4. Toán tử bit
| Toán tử | Tên | Ví dụ (thập phân) | Ví dụ (nhị phân) |
|---|---|---|---|
& | AND | 12 & 10 → 8 | 1100 & 1010 → 1000 |
| | OR | 12 | 10 → 14 | 1100 | 1010 → 1110 |
^ | XOR | 12 ^ 10 → 6 | 1100 ^ 1010 → 0110 |
~ | NOT (đảo bit) | ~12 → -13 | ~x == -x - 1 |
<< | Dịch trái | 3 << 2 → 12 | Nhân với 2**n |
>> | Dịch phải | 12 >> 2 → 3 | Chia nguyên cho 2**n |
& | ^ còn dùng cho set
Python nạp chồng ba toán tử này cho kiểu set: a | b là hợp,
a & b là giao, a ^ b là hiệu đối xứng. Từ Python 3.9,
| còn dùng để hợp nhất dict: {"a":1} | {"b":2}. Và trong type
hint, int | None nghĩa là "int hoặc None" (Chương 12). Cùng một ký hiệu, bốn ngữ cảnh —
Python dùng lại toán tử rất nhiều, hãy đọc theo kiểu dữ liệu hai bên.
11.5. Thứ tự ưu tiên
| Mức | Toán tử | Nhóm |
|---|---|---|
| 1 (cao nhất) | () | Ngoặc |
| 2 | ** | Luỹ thừa (kết hợp phải sang trái) |
| 3 | +x -x ~x | Đơn ngôi |
| 4 | * / // % | Nhân chia |
| 5 | + - | Cộng trừ |
| 6 | << >> | Dịch bit |
| 7 | & → ^ → | | Bit (theo thứ tự này) |
| 8 | == != < > <= >= is in | So sánh (chain được — xem mục 14) |
| 9 | not | Phủ định |
| 10 | and | Và |
| 11 | or | Hoặc |
| 12 (thấp nhất) | x if c else y, lambda | Biểu thức điều kiện |
# Đúng nhưng người đọc phải dừng lại nghĩ
if a and b or c and not d:
...
# ✅ Cùng ý nghĩa, đọc một lần là hiểu
if (a and b) or (c and not d):
...
# Trong Python, & và | ưu tiên CAO HƠN so sánh (==, <, ...)
if x & 1 == 0: # Python hiểu là (x & 1) == 0 → chạy đúng
if (x & 1) == 0: # ✅ vẫn nên viết ngoặc: rõ ý, không phải nhớ bảng ưu tiên
# ⚠️ C / JavaScript thì NGƯỢC lại: x & 1 == 0 là x & (1 == 0) → luôn 0.
# Thói quen viết ngoặc giúp code đúng ở mọi ngôn ngữ.
12. == 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 ==.
13. 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
13.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
...
14. 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: ...
15. 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 12 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 12 đà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 12 (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.