Chìa khóa của toàn bộ các vấn đề mã hóa như vậy là phải hiểu rằng về nguyên tắc có hai khái niệm khác biệt về "chuỗi" : (1) chuỗi ký tự và (2) chuỗi / mảng byte. Sự phân biệt này hầu như đã bị bỏ qua trong một thời gian dài vì tính phổ biến trong lịch sử của các bảng mã có không quá 256 ký tự (ASCII, Latin-1, Windows-1252, Mac OS Roman,…): các bảng mã này ánh xạ một tập hợp các ký tự phổ biến thành số từ 0 đến 255 (tức là byte); việc trao đổi tệp tương đối hạn chế trước khi web ra đời đã làm cho tình huống mã hóa không tương thích này có thể chấp nhận được, vì hầu hết các chương trình có thể bỏ qua thực tế là có nhiều mã hóa miễn là chúng tạo ra văn bản vẫn trên cùng một hệ điều hành: các chương trình như vậy sẽ đơn giản xử lý văn bản dưới dạng byte (thông qua mã hóa được sử dụng bởi hệ điều hành). Chế độ xem hiện đại, đúng đắn phân tách đúng đắn hai khái niệm chuỗi này, dựa trên hai điểm sau:
Nhân vật chủ yếu là không liên quan đến máy tính : người ta có thể vẽ chúng trên bảng phấn, v.v., chẳng hạn như بايثون, 中 蟒 và 🐍. "Ký tự" cho máy cũng bao gồm "hướng dẫn vẽ" ví dụ như dấu cách, dấu xuống dòng, hướng dẫn đặt hướng viết (đối với tiếng Ả Rập, v.v.), dấu, v.v. Một danh sách ký tự rất lớn được đưa vào tiêu chuẩn Unicode ; nó bao gồm hầu hết các ký tự đã biết.
Mặt khác, máy tính cần phải biểu diễn các ký tự trừu tượng theo một cách nào đó: đối với điều này, chúng sử dụng các mảng byte (bao gồm các số từ 0 đến 255), vì bộ nhớ của chúng có dạng các khối byte. Quá trình cần thiết để chuyển đổi các ký tự thành byte được gọi là (UTF-8, UTF-16,…) được Unicode xác định cho danh sách các ký tự của nó (Unicode do đó xác định cả danh sách các ký tự và mã hóa cho các ký tự này — vẫn còn những chỗ mà người ta coi cụm từ "mã hóa Unicode" như một cách để chỉ UTF-8 phổ biến, nhưng đây là thuật ngữ không chính xác, vì Unicode cung cấp mã hóa . Do đó, một máy tính yêu cầu một bảng mã để biểu diễn các ký tự. Bất kỳ văn bản nào hiện diện trên máy tính của bạn đều được mã hóa (cho đến khi nó được hiển thị), cho dù nó được gửi đến một thiết bị đầu cuối (mong đợi các ký tự được mã hóa theo một cách cụ thể) hay được lưu trong một tệp. Để được hiển thị hoặc được "hiểu" đúng cách (nói cách khác, trình thông dịch Python), các luồng byte được giải mã thành các ký tự. Một vài mã hóa nhiều bảng mã).
Tóm tắt, máy tính cần biểu diễn bên trong các ký tự bằng byte và chúng thực hiện điều đó thông qua hai hoạt động:
Mã hóa : ký tự → byte
Giải mã : byte → ký tự
Một số bảng mã không thể mã hóa tất cả các ký tự (ví dụ: ASCII), trong khi (một số) bảng mã Unicode cho phép bạn mã hóa tất cả các ký tự Unicode. Bảng mã cũng không nhất thiết phải là duy nhất , vì một số ký tự có thể được biểu diễn trực tiếp hoặc dưới dạng kết hợp (ví dụ: ký tự cơ sở và dấu).
Lưu ý rằng khái niệm dòng mới thêm một lớp phức tạp , vì nó có thể được biểu diễn bằng các ký tự (điều khiển) khác nhau phụ thuộc vào hệ điều hành (đây là lý do cho chế độ đọc tệp dòng mới phổ biến của Python ).
Bây giờ, cái mà tôi đã gọi là "ký tự" ở trên là cái mà Unicode gọi là " ký tự do người dùng cảm nhận ". Một ký tự do người dùng cảm nhận đôi khi có thể được biểu diễn bằng Unicode bằng cách kết hợp các phần ký tự (ký tự cơ sở, dấu trọng âm,…) được tìm thấy tại các chỉ mục khác nhau trong danh sách Unicode, được gọi là " điểm mã " —các điểm mã này có thể được kết hợp với nhau để tạo thành một "cụm grapheme". Do đó, Unicode dẫn đến khái niệm chuỗi thứ ba, được tạo thành từ một chuỗi các điểm mã Unicode, nằm giữa chuỗi byte và chuỗi ký tự, và gần với chuỗi ký tự sau hơn. Tôi sẽ gọi chúng là " chuỗi Unicode " (giống như trong Python 2).
Trong khi Python có thể in các chuỗi ký tự (do người dùng cảm nhận), các chuỗi không phải byte của Python về cơ bản là chuỗi các điểm mã Unicode , không phải các ký tự do người dùng cảm nhận. Các giá trị điểm mã là những giá trị được sử dụng trong cú pháp chuỗi của Python \u
và \U
Unicode. Không nên nhầm lẫn chúng với mã hóa của một ký tự (và không phải chịu bất kỳ mối quan hệ nào với nó: các điểm mã Unicode có thể được mã hóa theo nhiều cách khác nhau).
Điều này có một hệ quả quan trọng: độ dài của một chuỗi Python (Unicode) là số điểm mã của nó, không phải lúc nào cũng là số ký tự được người dùng cảm nhận : do đó s = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) cho 각 len 3
dùs
có một người dùng cảm nhận (tiếng Hàn) ký tự (bởi vì nó được biểu diễn bằng 3 điểm mã — ngay cả khi nó không bắt buộc, như print("\uac01")
hiển thị). Tuy nhiên, trong nhiều trường hợp thực tế, độ dài của một chuỗi là số ký tự mà người dùng cảm nhận được, vì nhiều ký tự thường được Python lưu trữ dưới dạng một điểm mã Unicode duy nhất.
Trong Python 2 , chuỗi Unicode được gọi là… "chuỗi Unicode" ( unicode
kiểu, dạng chữ u"…"
), trong khi mảng byte là "chuỗi" ( str
kiểu, ví dụ như mảng byte có thể được xây dựng bằng chuỗi ký tự "…"
). Trong Python 3 , chuỗi Unicode được gọi đơn giản là "chuỗi" ( str
kiểu, dạng chữ "…"
), trong khi mảng byte là "byte" ( bytes
kiểu, dạng chữ b"…"
). Do đó, một cái gì đó giống như "🐍"[0]
cho một kết quả khác trong Python 2 ( '\xf0'
, một byte) và Python 3 ( "🐍"
, ký tự đầu tiên và duy nhất).
Với một vài điểm chính này, bạn sẽ có thể hiểu hầu hết các câu hỏi liên quan đến mã hóa!
Thông thường, khi bạn in u"…"
tới một thiết bị đầu cuối , bạn sẽ không nhận được rác: Python biết mã hóa của thiết bị đầu cuối của bạn. Trên thực tế, bạn có thể kiểm tra mã hóa mà thiết bị đầu cuối mong đợi:
% python
Python 2.7.6 (default, Nov 15 2013, 15:20:37)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.2.79)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> print sys.stdout.encoding
UTF-8
Nếu các ký tự đầu vào của bạn có thể được mã hóa bằng mã hóa của thiết bị đầu cuối, Python sẽ làm như vậy và sẽ gửi các byte tương ứng đến thiết bị đầu cuối của bạn mà không phàn nàn. Sau đó, thiết bị đầu cuối sẽ cố gắng hết sức để hiển thị các ký tự sau khi giải mã các byte đầu vào (tệ nhất là phông chữ đầu cuối không có một số ký tự và thay vào đó sẽ in ra một số loại trống).
Nếu các ký tự đầu vào của bạn không thể được mã hóa bằng mã hóa của thiết bị đầu cuối, thì điều đó có nghĩa là thiết bị đầu cuối không được định cấu hình để hiển thị các ký tự này. Python sẽ phàn nàn (trong Python cóUnicodeEncodeError
vì chuỗi ký tự không thể được mã hóa theo cách phù hợp với thiết bị đầu cuối của bạn). Giải pháp khả thi duy nhất là sử dụng một thiết bị đầu cuối có thể hiển thị các ký tự (bằng cách định cấu hình thiết bị đầu cuối để nó chấp nhận một mã hóa có thể đại diện cho các ký tự của bạn hoặc bằng cách sử dụng một chương trình đầu cuối khác). Điều này rất quan trọng khi bạn phân phối các chương trình có thể được sử dụng trong các môi trường khác nhau: thông báo mà bạn in ra phải có thể biểu diễn được trong thiết bị đầu cuối của người dùng. Vì vậy, đôi khi tốt nhất là bám vào các chuỗi chỉ chứa các ký tự ASCII.
Tuy nhiên, khi bạn chuyển hướng hoặc chuyển đầu ra của chương trình, thì thường không thể biết mã hóa đầu vào của chương trình nhận là gì và đoạn mã trên trả về một số mã hóa mặc định: Không (Python 2.7) hoặc UTF-8 ( Python 3):
% python2.7 -c "import sys; print sys.stdout.encoding" | cat
None
% python3.4 -c "import sys; print(sys.stdout.encoding)" | cat
UTF-8
Tuy nhiên, mã hóa của stdin, stdout và stderr có thể được đặt thông qua PYTHONIOENCODING
biến môi trường, nếu cần:
% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8
Nếu việc in tới một thiết bị đầu cuối không tạo ra những gì bạn mong đợi, bạn có thể kiểm tra mã hóa UTF-8 mà bạn nhập theo cách thủ công có đúng không; chẳng hạn, ký tự đầu tiên của bạn ( \u001A
) không thể in được, nếu tôi không nhầm .
Tại http://wiki.python.org/moin/PrintFails , bạn có thể tìm thấy một giải pháp như sau, cho Python 2.x:
import codecs
import locale
import sys
# Wrap sys.stdout into a StreamWriter to allow writing unicode.
sys.stdout = codecs.getwriter(locale.getpreferredencoding())(sys.stdout)
uni = u"\u001A\u0BC3\u1451\U0001D10C"
print uni
Đối với Python 3, bạn có thể kiểm tra một trong các câu hỏi được hỏi trước đây trên StackOverflow.