Chuỗi và số — vì sao cộng hai kiểu lại nổ
Ô nhớ không chỉ giữ một giá trị, nó còn giữ một kiểu. Cộng chuỗi với số là chỗ người mới vấp sớm hơn ta tưởng — và là traceback đầu tiên bạn đọc trong khoá này.
TL;DR: Ô nhớ không chỉ giữ một giá trị, nó còn giữ một kiểu, và Python không tự ghép hai kiểu khác nhau lại với nhau. Cộng chuỗi "5" với số 3 không ra 8 như bạn tưởng — chương trình dừng lại bằng TypeError, và đây là traceback đầu tiên bạn đọc trong khoá này. Chuỗi "5" và số 5 nhìn giống hệt nhau trên màn hình nhưng là hai ô nhớ khác loại. Nút thắt hay gặp nhất: input() luôn trả về chuỗi, kể cả khi người dùng gõ toàn chữ số, nên muốn cộng được bạn phải tự đổi nó thành số bằng int(...) trước.
Bài trước, bạn đã thấy ô nhớ giữ một giá trị và tên biến trỏ đúng tới ô đó. Giờ thử đoạn này: bạn hỏi người dùng nhập tuổi bằng input(), rồi cộng thẳng với 1 để tính tuổi sang năm. Chương trình không chạy — nó dừng giữa chừng và in ra một khối chữ dài bạn chưa từng thấy.
Bài này giải thích vì sao: ô nhớ còn giữ thêm một thứ ngoài giá trị, và khi hai thứ đó không khớp nhau, Python thà dừng lại còn hơn đoán bừa.
1. Ô nhớ giữ thêm một thứ: kiểu
Bài trước bạn học: biến là cái tên gắn với một ô nhớ, và phép gán chép giá trị vào ô đó. Bài này thêm đúng một chi tiết: mỗi ô nhớ, ngoài giá trị, còn giữ một kiểu — nhãn nói ô đó đang chứa loại dữ liệu gì.
Nhớ lại tủ ký gửi ở bài 02: mỗi tủ có một tên nhãn và giữ một món đồ. Giờ hình dung thêm một chi tiết: tủ ký gửi ở một số cửa hàng có ngăn riêng cho hàng khô, ngăn riêng cho hàng đông lạnh — bỏ nhầm ngăn thì nhân viên từ chối nhận. Ô nhớ trong máy tính cũng vậy: nó không chỉ giữ một món đồ (giá trị), nó còn ghi rõ loại hàng được phép giữ trong ngăn đó (kiểu).
| Tủ ký gửi | Ô nhớ |
|---|---|
| Món đồ bỏ vào tủ | Giá trị, ví dụ 20 hoặc "5" |
| Loại ngăn: khô hay đông lạnh | Kiểu, ví dụ int hay str |
| Bỏ hàng khô vào ngăn đông lạnh, nhân viên từ chối | Cộng str với int, Python từ chối bằng TypeError |
Hai kiểu bạn đã chạm tới trong khoá: số nguyên (int) và chuỗi (str). Số là con số bạn tính toán, cộng trừ được. Chuỗi là một dãy ký tự nằm trong dấu ngoặc kép — kể cả khi ký tự đó là chữ số, nó vẫn chỉ là ký tự, không phải con số.

Ô nhớ ở trên giữ đủ hai thứ cùng lúc: giá trị 20 và kiểu int. Đến đây bạn chưa cần lo gì — vấn đề chỉ nảy ra khi hai ô nhớ khác kiểu bị đem cộng với nhau.
2. Predict — "5" + 3 in ra gì?
Trước khi đọc tiếp: đoạn dưới đây gán tong = "5" + 3 rồi in tong. Đừng chạy thử, viết ra một con số hoặc một câu bạn tin chắc sẽ xảy ra.
tong = "5" + 3
print(tong)
Chương trình không in ra 8, và cũng không in ra "53". Nó dừng lại ngay tại dòng gán, và màn hình hiện đúng như sau:
Traceback (most recent call last):
File "vidu.py", line 1, in <module>
tong = "5" + 3
~~~~^~~
TypeError: can only concatenate str (not "int") to str
Bạn chưa cần đọc hết năm dòng này. Chỉ dòng cuối là đáng đọc lúc này: TypeError nói loại lỗi là lỗi về kiểu dữ liệu, và ngay sau đó Python gọi tên đúng hai kiểu vừa va nhau — str (chuỗi) và int (số). Nói cách khác: + giữa một chuỗi và một số không có nghĩa rõ ràng, nên Python không đoán hộ bạn, nó dừng chương trình lại.
Đây là traceback đầu tiên bạn gặp trong khoá. Cách đọc đầy đủ một traceback — từng dòng file, từng lệnh gọi — sẽ có một bài riêng sau; lúc này biết đọc dòng cuối là đủ.
3. "5" và 5 — giống trên màn hình, khác trong ô nhớ
Bạn gán x = "5" và y = 5. Cả hai đều hiện ra đúng ký tự 5 khi bạn print, nhưng ô nhớ của chúng không giống nhau chút nào.

| Viết trong code | Python đọc là gì |
|---|---|
5 (kiểu int) | một con số tính toán được |
"5" (kiểu str) | một ký tự 5, không phải con số |
Sự khác biệt này nằm ở chính nghĩa của toán tử +: giữa hai số, nó cộng; giữa hai chuỗi, nó nối liền hai chuỗi lại. "5" + 3 đứng giữa hai nghĩa đó — một bên là chuỗi, một bên là số — nên Python từ chối đoán bạn muốn cộng hay muốn nối. Đó chính là traceback bạn vừa thấy.
4. input() trả về gì, kể cả khi bạn gõ số?
Trước khi nói tiếp, cần một định nghĩa: một hàm (function) là một khối việc có tên, có thể nhận đầu vào, và quan trọng nhất, nó trả về một giá trị cho chỗ gọi nó dùng tiếp. Khác với print(...) mà bài 01 gọi là một lệnh: lệnh chỉ thực hiện một hành động (in ra màn hình) rồi thôi, không đưa lại thứ gì để dùng tiếp. Hàm thì có — gọi hàm xong, bạn nhận về một giá trị, y hệt như kết quả của một phép tính.
Bài 02 đã nói: vế phải của phép gán được tính xong trước, rồi giá trị mới được chép vào ô nhớ. Một lời gọi hàm cũng đứng được ở vế phải theo đúng quy tắc đó — máy chạy xong hàm, lấy giá trị nó trả về, rồi mới gán. input(...) chính là một hàm như vậy.
Hàm input() luôn trả về một chuỗi, bất kể người dùng gõ chữ hay số.
tuoi_text = input("Ban bao nhieu tuoi? ")
Dù người dùng gõ 20, biến tuoi_text vẫn giữ chuỗi "20", không phải số 20. input() không tự đoán ý bạn — nó chỉ đưa lại nguyên văn những gì được gõ, dưới dạng ký tự.
Đây là chỗ hầu hết người mới gặp TypeError lần đầu: lấy thẳng kết quả input() đem cộng với một số, quên mất nó vẫn còn là chuỗi.
Đoạn dưới muốn tính tuổi sang năm bằng cách cộng thêm 1. Một chỗ bị để trống — tự điền trước khi xem đáp án.
tuoi_text = input("Ban bao nhieu tuoi? ")
# tuoi_text la mot chuoi, du ban go toan chu so
tuoi_sang_nam = ____ + 1 # dien vao cho trong
print(tuoi_sang_nam)
Gợi ý: bạn cần đổi tuoi_text từ chuỗi sang số trước khi cộng được. Hàm nào trong bài này làm việc đó?
Đáp án:
tuoi_text = input("Ban bao nhieu tuoi? ")
tuoi_sang_nam = int(tuoi_text) + 1
print(tuoi_sang_nam)
int(...) đọc một chuỗi và trả về số nguyên tương ứng. Sau dòng đó, ô nhớ tuoi_sang_nam được tính từ một số thật, nên + 1 cộng bình thường, không còn trộn kiểu.
5. Liên hệ các bài khác
- Bài 02 — Biến và phép gán — ô nhớ và phép gán là nền để hiểu vì sao ô nhớ còn giữ thêm kiểu.
- Bài 01 — Chương trình là dãy lệnh — máy vẫn thi hành đúng từng dòng, kể cả dòng gây ra lỗi kiểu như trên.
- Bài 04 — Bảng trace — khi trace tay một chương trình, bạn sẽ ghi cả kiểu lẫn giá trị của mỗi biến sau từng dòng.
6. Tóm tắt
- Thấy
TypeErrorkhi cộng hai thứ "trông như số"? Nghi ngay một bên đang là chuỗi — thường do quênint()sauinput(). - Đọc dòng cuối traceback trước tiên; nó nêu thẳng hai kiểu đang va chạm, chưa cần đọc mấy dòng phía trên.
- Trước khi cộng/trừ bất cứ thứ gì lấy từ
input(), tự hỏi: giá trị này đã quaint(...)chưa? "5"và5viết gần giống nhau; khi debug, để ý dấu ngoặc kép để biết bạn đang cầm chuỗi hay số.- Hàm khác lệnh ở chỗ nó trả về một giá trị dùng tiếp được — đó là lý do
input()vàint(...)đứng được ở vế phải phép gán.
7. Tự kiểm tra
- Q1Vì sao 5 + 3 chạy được nhưng "5" + 3 lại dừng chương trình bằng lỗi?
- Q2Dòng cuối traceback
TypeError: can only concatenate str (not "int") to strcho bạn biết điều gì, dù bạn chưa đọc hết các dòng phía trên? - Q3Bạn gán x = "5" và y = 5. Hai ô nhớ này khác nhau ở đâu, dù cả hai đều hiện chữ số 5 như nhau?
- Q4Bạn viết
ten_input = input(...), dù người dùng gõ toàn chữ số như 2005,ten_inputmang kiểu gì? - Q5Muốn cộng kết quả của input() với một số nguyên mà không bị TypeError, bạn cần làm gì trước khi cộng?
- Q6Bạn viết
tuoi = 2026 - nam_sinhmà quên rằngnam_sinhvẫn còn là chuỗi từinput(). Chuyện gì xảy ra khi chạy dòng này?
Bài tiếp theo: Bảng trace
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
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