Lỗi Gặp Phải
Bạn đang đọc một file văn bản trong Python trên Windows và đột nhiên gặp lỗi này:
UnicodeDecodeError: 'charmap' codec can't decode byte 0x9d in position 10859: character maps to <undefined>
File mở bình thường trong Notepad hay VS Code, nhưng Python lại từ chối đọc. Nguyên nhân chỉ xoay quanh một điều: encoding mặc định.
Nguyên Nhân Gốc Rễ
Hàm open() của Python trên Windows mặc định dùng encoding theo locale hệ thống — thường là cp1252 (Windows-1252, còn gọi là charmap). Encoding này chỉ hỗ trợ một tập con các ký tự Latin.
Khi file chứa các byte nằm ngoài phạm vi đó — văn bản UTF-8, file từ codepage khác, bất cứ thứ gì có emoji hay Unicode mở rộng — Python sẽ ném ra UnicodeDecodeError. Byte 0x9d là ví dụ điển hình: hợp lệ trong UTF-8, nhưng không xác định trong cp1252.
Trên macOS và Linux, lỗi này hiếm khi xảy ra. Mặc định của chúng đã là UTF-8. Windows mới là trường hợp ngoại lệ.
Cách Sửa 1: Chỉ Định Encoding UTF-8 (Giải Quyết 90% Trường Hợp)
Chỉ cần thêm encoding='utf-8' vào lời gọi open():
# Trước đây (bị lỗi trên Windows)
with open('data.txt') as f:
content = f.read()
# Sau khi sửa (hoạt động trên mọi nền tảng)
with open('data.txt', encoding='utf-8') as f:
content = f.read()
Nếu file được lưu dưới dạng UTF-8 — điều mà hầu hết các công cụ hiện đại mặc định làm — thì đây là tất cả những gì bạn cần. Chỉ thay đổi một dòng.
Kiểm tra xem đã sửa được chưa
with open('data.txt', encoding='utf-8') as f:
content = f.read()
print(f"Đã đọc {len(content)} ký tự thành công")
Không còn exception nào. Đã sửa xong.
Cách Sửa 2: Khi Bạn Không Biết Encoding Là Gì
Nhận file từ khách hàng hay hệ thống bên thứ ba? Encoding có thể là bất cứ thứ gì — cp1252, latin-1, shift-jis, GBK. Dùng chardet để tự động phát hiện.
pip install chardet
import chardet
# Đọc dữ liệu thô dạng bytes trước
with open('data.txt', 'rb') as f:
raw = f.read()
result = chardet.detect(raw)
detected_encoding = result['encoding']
confidence = result['confidence']
print(f"Phát hiện: {detected_encoding} (độ tin cậy: {confidence:.0%})")
# Giờ đọc file với encoding đã phát hiện
with open('data.txt', encoding=detected_encoding) as f:
content = f.read()
Độ tin cậy dưới 70%? File có thể bị hỏng, dùng nhiều encoding lẫn lộn, hoặc từ một codepage rất hiếm gặp. Hãy xác nhận lại với người gửi trước khi xử lý dữ liệu đầu ra.
Cách Sửa 3: Bỏ Qua Hoặc Thay Thế Các Byte Lỗi
Cần đọc file nhanh và không quan tâm đến vài ký tự không đọc được? Tham số errors sẽ xử lý việc này:
# Thay thế các byte không giải mã được bằng ký tự thay thế (U+FFFD)
with open('data.txt', encoding='utf-8', errors='replace') as f:
content = f.read()
# Hoặc bỏ qua chúng hoàn toàn
with open('data.txt', encoding='utf-8', errors='ignore') as f:
content = f.read()
Lưu ý: errors='ignore' âm thầm loại bỏ dữ liệu. Nếu bạn đang xử lý hồ sơ khách hàng hay xuất dữ liệu tài chính, bạn sẽ không bao giờ biết phần nào đã bị mất. Chỉ dùng tùy chọn này khi các ký tự không đọc được thực sự không quan trọng.
Cách Sửa 4: Bắt Buộc UTF-8 Cho Toàn Bộ Tiến Trình
Chạy script có các thao tác file rải rác khắp nơi? Đặt PYTHONUTF8=1 một lần và mọi lời gọi open() sẽ tự động mặc định UTF-8:
# Command Prompt
set PYTHONUTF8=1
python your_script.py
# PowerShell
$env:PYTHONUTF8 = "1"
python your_script.py
Hoặc truyền trực tiếp bằng cờ -X:
python -X utf8 your_script.py
Chế độ UTF-8 áp dụng cho toàn bộ tiến trình — I/O file, stdin, stdout, stderr, tất cả mọi thứ. Có sẵn từ Python 3.7 trở lên.
Để thiết lập vĩnh viễn, thêm PYTHONUTF8=1 vào biến môi trường Windows: System Properties → Advanced → Environment Variables → New (trong mục "User variables").
Cách Sửa 5: latin-1 Cho Các File Windows Cũ
Một số file cũ — xuất từ cơ sở dữ liệu Access đời cũ, CRM cổ xưa, cổng dữ liệu chính phủ — thực sự dùng cp1252 hoặc latin-1. Nếu chardet xác nhận điều này, hãy đọc với codec phù hợp:
with open('legacy_file.txt', encoding='cp1252') as f:
content = f.read()
# latin-1 ánh xạ mọi byte 0x00-0xFF sang một ký tự Unicode
# nên nó không bao giờ ném ra UnicodeDecodeError
with open('mystery_file.txt', encoding='latin-1') as f:
content = f.read()
Latin-1 chấp nhận mọi giá trị byte có thể có mà không gây lỗi — đó là điều khiến nó hữu ích làm phương án dự phòng. Điểm trừ: nếu file thực sự là UTF-8, đầu ra sẽ bị hiển thị sai ký tự. Hãy dùng nó cuối cùng, không phải đầu tiên.
Kiểm Tra Encoding Mặc Định Của Hệ Thống
Không chắc Python đang mặc định encoding gì trên máy của bạn? Chạy lệnh này:
import sys
import locale
print(sys.getdefaultencoding()) # thường là 'utf-8'
print(sys.getfilesystemencoding()) # 'utf-8' trên Mac/Linux, 'mbcs' trên Windows
print(locale.getpreferredencoding()) # encoding mà open() thực sự dùng — thường là 'cp1252'
Nếu locale.getpreferredencoding() trả về cp1252, đó chính là thủ phạm. Mọi lời gọi open() không có encoding= tường minh đều dùng nó — một cách âm thầm, không có cảnh báo nào.
Phòng Ngừa
- Luôn viết
encoding='utf-8'trong mọi lời gọiopen(). Hãy làm vậy ngay cả trên Linux và macOS nơi UTF-8 là mặc định — code của bạn sẽ có ngày chạy trên Windows. - Lưu file dưới dạng UTF-8 trong trình soạn thảo. VS Code mặc định UTF-8. Notepad trên Windows 10 và 11 cũng đã mặc định UTF-8, nhưng các phiên bản cũ hơn mặc định ANSI/cp1252.
- Nếu bạn xuất bản một thư viện, hãy nhận tham số
encodingtrong bất kỳ hàm nào đọc file. Ghi lại rõ giá trị mặc định là gì. - Cảnh báo
W1514của Pylint (unspecified-encoding) sẽ gắn cờ mọi lời gọiopen()không có encoding tường minh. Tích hợp nó vào pipeline CI và bạn sẽ phát hiện những lỗi này trước khi chúng lên production.

