Nhập môn Tư duy Lập trình/Điều kiện phức hợp — and, or, not
10/31
Bài 10 / 31~11 phútRẽ nhánh & lặpMiễn phí lượt xem

Điều kiện phức hợp — and, or, not

Ghép nhiều điều kiện lại là chỗ ba cái bẫy kinh điển nằm chờ. Chúng không báo lỗi, chương trình vẫn chạy — chỉ là chạy sai, và người mới mất hàng giờ mới thấy.

TL;DR: and, or, not ghép nhiều câu hỏi thành một. Cái bẫy là chúng trông giống tiếng Việt nên người ta viết theo nhịp nói: if x == 1 or 2 đọc lên nghe hệt "nếu x bằng 1 hoặc 2", nhưng máy tách quanh chữ or và thấy vế phải là số 2 — mà số khác 0 luôn được coi là đúng, nên cả điều kiện luôn đúng với mọi x. Hai bẫy còn lại cùng một họ: phủ định sai chỗ, và so sánh một chuỗi với một số. Cả ba đều không báo lỗi dòng nào. Cách thoát duy nhất là kiểm từng vế xem nó có tự đứng thành một câu hỏi hoàn chỉnh không.

Một câu hỏi không phải lúc nào cũng đủ. "Được giảm giá nếu là thành viên đơn trên hai trăm nghìn." "Báo lỗi nếu tên rỗng hoặc mật khẩu ngắn quá."

Ghép câu hỏi thì dễ. Vấn đề là ba cách ghép sai dưới đây chạy hoàn toàn êm — không lỗi cú pháp, không traceback, không cảnh báo. Chương trình cứ thế cho ra kết quả sai, ngày này qua ngày khác.

1. Analogy — ba cái công tắc

Hình dung một cánh cửa có hai công tắc nối nhau. Nối kiểu and thì cửa chỉ mở khi cả hai công tắc cùng bật; một cái tắt là cửa đóng. Nối kiểu or thì chỉ cần một công tắc bật là cửa mở. Còn not là cái công tắc gắn ngược: bật thành tắt, tắt thành bật.

Cách nốiCửa mở khiTrong code
Nối tiếp — cả hai phải bậtCả hai đúnga and b
Nối song song — một cái bật là đủÍt nhất một đúnga or b
Gắn ngượcVế kia sainot a

Điểm quan trọng của analogy này: mỗi công tắc phải là một công tắc thật. Bạn không thể nối một cánh cửa vào "công tắc và cái bóng đèn" — cái bóng đèn không phải công tắc. Đúng cái nhầm đó là bẫy thứ nhất bên dưới.

2. Ba phép ghép, viết ra cho rõ

la_thanh_vien = True
tong_don = 250000

# Ca hai deu phai dung
print(la_thanh_vien and tong_don >= 200000)

# Chi can mot ve dung
print(la_thanh_vien or tong_don >= 500000)

# Lat nguoc ket qua cua ve dung sau no
print(not la_thanh_vien)

Output thật:

True
True
False
Nhớ lại Bài 1 — Rẽ nhánh

Điều kiện là một câu hỏi cho ra True hoặc False, không phải một mệnh lệnh. Bài này ghép nhiều câu hỏi thành một, và mọi lỗi dưới đây đều sinh ra từ việc một vế không thật sự là câu hỏi.

3. Vì sao if x == 1 or 2 luôn đúng?

Đây là lỗi kinh điển nhất, và nó xuất hiện vì câu code đọc lên nghe đúng y như tiếng Việt: "nếu x bằng 1 hoặc 2".

Máy không đọc theo nhịp nói. Nó tách quanh chữ or thành hai vế độc lập:

Sơ đồ ba tầng: hộp nét đứt trên cùng ghi if x == 1 or 2 kèm chú thích bạn viết và máy tách thành hai vế quanh chữ or; tầng giữa có hai hộp, hộp xanh lá bên trái ghi x == 1 là một câu hỏi thật cho ra đúng hoặc sai, hộp cam bên phải ghi số 2 không phải câu hỏi chỉ là con số 2; mũi tên cam từ hộp bên phải đổ xuống hộp cam dưới cùng ghi số khác 0 luôn được coi là ĐÚNG nên cả điều kiện luôn đúng với mọi giá trị của x

Vế trái x == 1 là một câu hỏi tử tế. Vế phải chỉ là con số 2 — nó không hỏi gì cả.

Vậy máy làm gì với một con số đứng ở chỗ cần câu hỏi? Nó áp một quy tắc có sẵn: tài liệu Python có một danh sách những thứ được coi là sai, trong đó có None, False, số 0 của mọi kiểu số, và các chuỗi hay danh sách rỗng; rồi nói rằng mặc định mọi thứ khác được coi là đúng. (Danh sách gốc còn vài kiểu dữ liệu khoá này chưa dạy tới.)

Số 2 không nằm trong danh sách sai. Nên nó là đúng. Nên x == 1 or 2 là "sai-hoặc-đúng", tức là luôn đúng, với mọi giá trị của x.

x = 7

if x == 1 or 2:
    print("x la 1 hoac 2")

Output thật:

x la 1 hoac 2

Bảy không phải một, cũng không phải hai — vậy mà chương trình vẫn in ra, không một dòng lỗi nào.

Cách viết đúng là lặp lại phần bị lược: if x == 1 or x == 2. Nhìn dài dòng hơn tiếng Việt, nhưng mỗi vế giờ tự đứng được thành một câu hỏi.

💡 Cách nhớ

Che tay lên phần trước chữ or. Phần còn lại có đọc ra thành một câu hỏi có/không được không? Nếu không, bạn đang viết sai.

4. Thử đoán — phủ định đặt sai chỗ

Bẫy thứ hai. Một cửa hàng muốn báo lỗi khi khách không phải thành viên đơn dưới hai trăm nghìn:

la_thanh_vien = False
tong_don = 300000

if not la_thanh_vien and tong_don < 200000:
    print("Khong duoc giam gia")
else:
    print("Duoc giam gia")
🖊️ Thử đoán

Chương trình in ra dòng nào? Viết ra giấy, kèm một câu giải thích not đang phủ định cái gì — chỉ mình la_thanh_vien, hay cả cụm phía sau nó?

Kết quả

5. Bẫy thứ ba — so sánh chuỗi với số

Ô nhớ giữ đồng thời giá trị và kiểu. Ký tự "5" và số 5 là hai thứ khác nhau, và cộng chúng lại thì chương trình dừng với traceback. Bài này cho thấy cùng ranh giới đó gây ra chuyện gì khi nó rơi vào một điều kiện.

Ở module trước, cộng chuỗi với số làm chương trình nổ ngay. Trong điều kiện thì tuỳ phép so sánh — và đó mới là chỗ nguy hiểm.

tuoi = "20"

# So sanh bang: chay binh thuong, khong loi
print(tuoi == 20)

Output thật:

False

Không lỗi. Chỉ là False — mãi mãi False, kể cả khi người dùng nhập đúng số 20, vì một chuỗi không bao giờ bằng một số. Một điều kiện tuổi tác viết kiểu này sẽ âm thầm từ chối mọi người dùng.

Đổi sang so sánh lớn bé thì lại nổ:

tuoi = "20"

if tuoi >= 18:
    print("Du tuoi")

Traceback thật:

Traceback (most recent call last):
  File "vidu.py", line 3, in <module>
    if tuoi >= 18:
       ^^^^^^^^^^
TypeError: '>=' not supported between instances of 'str' and 'int'

Dòng dấu mũ chỉ thẳng vào biểu thức gây lỗi chỉ có từ Python 3.11 trở lên; bản cũ hơn vẫn báo đúng lỗi đó nhưng không kèm dòng này.

Hai hành vi khác hẳn nhau từ cùng một lỗi gốc. == giữa hai kiểu khác nhau chỉ trả về False; còn >=, <, > thì dừng chương trình. Cái im lặng nguy hiểm hơn cái nổ nhiều — cái nổ ít nhất còn chỉ đúng dòng cho bạn.

Chỗ này dễ sập bẫy nhất khi dữ liệu đến từ input(), vì input() luôn cho ra chuỗi, kể cả khi người dùng gõ toàn chữ số.

6. Tới lượt bạn — tìm bug

Yêu cầu: cho trẻ vào khu vui chơi nếu cao từ 120cm trở lên, hoặc nếu đi kèm người lớn. Đoạn dưới đây in ra đúng kết quả mong đợi — và vẫn có bug.

🐛 Tìm bug trong code này
chieu_cao = 105
co_nguoi_lon = False

if chieu_cao >= 120 or co_nguoi_lon == True or 0:
  print("Duoc vao")
else:
  print("Chua duoc vao")

7. 📚 Đào sâu — quy tắc đúng/sai của Python

📚 Đào sâu (không bắt buộc)

Built-in Types — Python documentation — mục "Truth Value Testing" là nguồn của quy tắc ở mục 3: nó liệt kê đúng những giá trị được coi là sai (None, False, số 0 của mọi kiểu số, chuỗi và tập rỗng) rồi nói mặc định mọi thứ khác được coi là đúng. Đọc mục này một lần là bạn hiểu vì sao rất nhiều điều kiện hỏng lại chạy im lặng thay vì báo lỗi.

Cùng trang, mục "Boolean Operations" ghi x or y nghĩa là "nếu x đúng thì lấy x, không thì lấy y", và nói rõ đây là toán tử ngắn mạch: vế phải chỉ được tính khi vế trái sai.

8. Liên hệ các bài khác

  • Bài 1 — Rẽ nhánh — điều kiện là một biểu thức đúng/sai. Cả ba bẫy ở bài này đều là hệ quả của việc quên mất điều đó.
  • Bài 3 module trước — Chuỗi và số — ranh giới kiểu đã dựng ở đó, mục 5 bài này cho thấy nó cắn thế nào bên trong một điều kiện.
  • Bài 3 — Vòng lặp while — điều kiện dừng của vòng lặp cũng là một biểu thức y hệt, nên viết sai kiểu này ở đó sẽ thành vòng lặp chạy mãi.
  • Bài 5 — Lặp lồng nhau — lỗi lệch một đơn vị ở đó cũng thuộc họ "chạy êm nhưng sai", cùng loại nguy hiểm với ba bẫy tại đây.

9. Tóm tắt

  • Che tay lên phần trước and hoặc or: phần còn lại phải tự đọc ra thành một câu hỏi có/không.
  • x == 1 or 2 luôn đúng vì số 2 không nằm trong danh sách giá trị được coi là sai.
  • not chỉ phủ định vế ngay sau nó; muốn phủ định cả cụm thì phải đóng ngoặc.
  • == giữa chuỗi và số chỉ trả về False và không báo gì; >= thì dừng hẳn với TypeError.
  • input() luôn cho ra chuỗi — mọi so sánh với số sau đó đều dính bẫy mục 5.

10. Tự kiểm tra

Tự kiểm tra
0/6 câu đã trả lời
  1. Q1
    Vì sao if x == 1 or 2 đúng với mọi giá trị của x, kể cả x = 7?
  2. Q2
    Nếu đổi thành if x == 1 or 0 thì chương trình còn sai nữa không? Đây có phải cách sửa đúng không?
  3. Q3
    Trong not la_thanh_vien and tong_don < 200000, not đang phủ định cái gì? Muốn phủ định cả cụm thì viết thế nào?
  4. Q4
    Vì sao "20" == 20 chỉ cho ra False mà không báo lỗi, trong khi "20" >= 18 lại dừng chương trình?
  5. Q5
    Vì sao lỗi ở mục 5 hay xuất hiện nhất với dữ liệu lấy từ input()?
  6. Q6
    Cả ba lỗi trong bài đều không làm chương trình báo lỗi cú pháp. Vậy cách tự kiểm tra nào bắt được chúng trước khi chạy?

Bài tiếp theo: Vòng lặp while — cái gì đổi mỗi vòng, khi nào dừng

Bài này đáng gửi cho bạn học cùng?

Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.

Bài này có giúp bạn hiểu bản chất không?

Hỏi đáp về bài này

Chưa có câu hỏi

Đặt câu hỏi

Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).

Đặt câu hỏi đầu tiên

Bài tiếp theo

Vòng lặp while — cái gì đổi mỗi vòng, khi nào dừng