Python str và các loại unicode


101

Làm việc với Python 2.7, tôi tự hỏi có lợi thế thực sự nào khi sử dụng kiểu unicodethay vì str, vì cả hai đều có thể giữ các chuỗi Unicode. Có lý do đặc biệt nào ngoài việc có thể đặt mã Unicode trong unicodechuỗi bằng cách sử dụng ký tự thoát \không ?:

Thực thi một mô-đun với:

# -*- coding: utf-8 -*-

a = 'á'
ua = u'á'
print a, ua

Kết quả bằng: á, á

BIÊN TẬP:

Thử nghiệm thêm bằng cách sử dụng Python shell:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

Vì vậy, unicodechuỗi dường như được mã hóa bằng cách sử dụng latin1thay vì utf-8và chuỗi thô được mã hóa bằng cách sử dụng utf-8? Tôi còn bối rối hơn bây giờ! :S


Không có mã hóa cho unicode, nó chỉ là sự trừu tượng của ký tự unicode; unicodecó thể được chuyển đổi sang strmột số mã hóa (ví dụ utf-8).
Bin

Câu trả lời:


178

unicodelà để xử lý văn bản . Văn bản là một chuỗi các điểm mãcó thể lớn hơn một byte đơn . Văn bản có thể được mã hóa trong một mã hóa cụ thể để đại diện cho văn bản như byte thô (ví dụ utf-8, latin-1...).

Lưu ý rằng unicode không được mã hóa ! Biểu diễn nội bộ được sử dụng bởi python là một chi tiết triển khai và bạn không nên quan tâm đến nó miễn là nó có thể đại diện cho các điểm mã bạn muốn.

Ngược lại strtrong Python 2 là một chuỗi byte đơn giản . Nó không đại diện cho văn bản!

Bạn có thể unicodecoi đây là một đại diện chung của một số văn bản, có thể được mã hóa theo nhiều cách khác nhau thành một chuỗi dữ liệu nhị phân được biểu diễn qua str.

Lưu ý: Trong Python 3, unicodeđã được đổi tên thành strvà có một byteskiểu mới cho một chuỗi byte đơn giản.

Một số khác biệt mà bạn có thể thấy:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte

Lưu ý rằng khi sử dụng, strbạn có quyền điều khiển cấp thấp hơn trên từng byte đơn của một biểu diễn mã hóa cụ thể, trong khi sử dụng, unicodebạn chỉ có thể điều khiển ở cấp điểm mã. Ví dụ bạn có thể làm:

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

Những gì trước đây là UTF-8 hợp lệ, không còn nữa. Sử dụng một chuỗi unicode, bạn không thể thao tác theo cách mà chuỗi kết quả không phải là văn bản unicode hợp lệ. Bạn có thể xóa một điểm mã, thay thế một điểm mã bằng một điểm mã khác, v.v. nhưng bạn không thể gây rối với biểu diễn bên trong.


4
Cảm ơn rất nhiều cho câu trả lời của bạn, nó đã giúp rất nhiều! Phần làm rõ nhất đối với tôi là: "unicode không được mã hóa! Biểu diễn bên trong được sử dụng bởi python là chi tiết triển khai và bạn không nên quan tâm đến nó [...]". Vì vậy, khi tuần tự hóa unicodecác đối tượng, tôi đoán trước tiên chúng ta phải xác định rõ ràng encode()chúng sang định dạng mã hóa thích hợp, vì chúng ta không biết cái nào đang được sử dụng bên trong để biểu diễn unicodegiá trị.
Caumons

10
Đúng. Khi bạn muốn lưu một số văn bản (ví dụ: vào một tệp), bạn phải biểu diễn nó bằng byte, tức là bạn phải mã hóa nó. Khi truy xuất nội dung, bạn nên biết mã hóa đã được sử dụng, để có thể giải mã các byte thành một unicodeđối tượng.
Bakuriu

Tôi xin lỗi, nhưng câu lệnh unicodekhông được mã hóa rõ ràng là sai. UTF-16 / UCS-2 và UTF-32 / UCS-4 cũng là các mã hóa ... và trong tương lai có thể sẽ tạo ra nhiều mã hóa hơn nữa. Điểm là, chỉ vì bạn không nên quan tâm đến chi tiết triển khai (và thực sự là bạn không nên!), Vẫn không có nghĩa là nó unicodekhông được mã hóa. Tất nhiên là như vậy. Cho dù nó có thể là .decode()'d là một câu chuyện hoàn toàn khác.
0xC0000022L

1
@ 0xC0000022L Có thể câu vì nó không rõ ràng. Nó phải nói rằng: unicodebiểu diễn bên trong đối tượng có thể là bất cứ thứ gì nó muốn, bao gồm cả biểu diễn không chuẩn. Đặc biệt trong python3 + unicode không sử dụng một đại diện nội bộ không chuẩn mà cũng thay đổi tùy thuộc vào dữ liệu được chứa. Vì vậy, nó không phải là một mã hóa tiêu chuẩn . Unicode như một tiêu chuẩn văn bản chỉ định nghĩa các điểm mã là một đại diện trừu tượng của văn bản, có rất nhiều cách để mã hóa unicode trong bộ nhớ bao gồm tiêu chuẩn utf-X, v.v. Python sử dụng cách riêng của nó để hiệu quả.
Bakuriu

1
@ 0xC0000022L Ngoài ra, thực tế là UTF-16 là một mã hóa không liên quan gì đến unicodeđối tượng của CPython , vì nó không sử dụng UTF-16 hay UTF-32. Nó sử dụng một biểu diễn đặc biệt và nếu bạn muốn mã hóa dữ liệu thành các byte thực tế, bạn phải sử dụng encode. Ngoài ra: ngôn ngữ không bắt buộc cách thức unicodeđược triển khai, vì vậy các phiên bản hoặc cách triển khai khác nhau của python có thể (và ) biểu diễn nội bộ khác nhau.
Bakuriu

38

Unicode và bảng mã là những thứ hoàn toàn khác nhau, không liên quan đến nhau.

Unicode

Gán một ID số cho mỗi ký tự:

  • 0x41 → A
  • 0xE1 → á
  • 0x414 → Д

Vì vậy, Unicode chỉ định số 0x41 cho A, 0xE1 cho á và 0x414 cho Д.

Ngay cả mũi tên nhỏ → tôi đã sử dụng cũng có số Unicode của nó, đó là 0x2192. Và ngay cả các biểu tượng cảm xúc cũng có số Unicode, 😂 là 0x1F602.

Bạn có thể tra cứu số Unicode của tất cả các ký tự trong bảng này . Đặc biệt, bạn có thể tìm thấy ba ký tự đầu tiên ở trên tại đây , mũi tên ở đây và biểu tượng cảm xúc tại đây .

Các số này được Unicode gán cho tất cả các ký tự được gọi là điểm mã .

Mục đích của tất cả điều này là cung cấp một phương tiện để tham chiếu rõ ràng đến từng ký tự. Ví dụ: nếu tôi đang nói về 😂, thay vì nói "bạn biết đấy, biểu tượng cảm xúc cười ra nước mắt này" , tôi chỉ có thể nói, mã Unicode điểm 0x1F602 . Dễ dàng hơn, phải không?

Lưu ý rằng các điểm mã Unicode thường được định dạng bằng dấu đứng đầu U+, sau đó là giá trị số thập lục phân được đệm ít nhất 4 chữ số. Vì vậy, các ví dụ trên sẽ là U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

Điểm mã Unicode nằm trong khoảng từ U + 0000 đến U + 10FFFF. Đó là 1.114.112 số. 2048 trong số này được sử dụng để thay thế , do đó, vẫn còn 1.112.064. Điều này có nghĩa là, Unicode có thể gán một ID duy nhất (điểm mã) cho 1.112.064 ký tự riêng biệt. Không phải tất cả các điểm mã này đều được gán cho một ký tự và Unicode được mở rộng liên tục (ví dụ: khi các biểu tượng cảm xúc mới được giới thiệu).

Điều quan trọng cần nhớ là tất cả những gì Unicode làm là gán một ID số, ​​được gọi là điểm mã, cho mỗi ký tự để dễ dàng tham khảo và rõ ràng.

Mã hóa

Ánh xạ các ký tự thành các mẫu bit.

Các mẫu bit này được sử dụng để biểu diễn các ký tự trong bộ nhớ máy tính hoặc trên đĩa.

Có nhiều bảng mã khác nhau bao gồm các tập con ký tự khác nhau. Trong thế giới nói tiếng Anh, các bảng mã phổ biến nhất là:

ASCII

Maps 128 ký tự (điểm mã U + 0000 đến U + 007F) đến mẫu bit có độ dài 7.

Thí dụ:

  • a → 1100001 (0x61)

Bạn có thể xem tất cả các ánh xạ trong bảng này .

ISO 8859-1 (còn gọi là tiếng Latinh-1)

Maps 191 ký tự (điểm mã U + 0020 đến U + 007E và U + 00A0 đến U + 00FF) để mẫu bit có độ dài 8.

Thí dụ:

  • a → 01100001 (0x61)
  • á → 11100001 (0xE1)

Bạn có thể xem tất cả các ánh xạ trong bảng này .

UTF-8

Maps 1.112.064 ký tự (tất cả hiện Unicode điểm code) để mô hình chút hoặc chiều dài 8, 16, 24 hoặc 32 bit (có nghĩa là, 1, 2, 3, hoặc 4 byte).

Thí dụ:

  • a → 01100001 (0x61)
  • á → 11000011 10100001 (0xC3 0xA1)
  • ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
  • 😂 → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)

Cách UTF-8 mã hóa các ký tự thành chuỗi bit được mô tả rất tốt ở đây .

Unicode và mã hóa

Nhìn vào các ví dụ trên, có thể thấy rõ Unicode hữu ích như thế nào.

Ví dụ: nếu tôi là người Latin-1 và tôi muốn giải thích mã hóa của mình là á, tôi không cần phải nói:

"Tôi mã hóa rằng a bằng aigu (hoặc tuy nhiên bạn gọi thanh tăng đó) là 11100001"

Nhưng tôi chỉ có thể nói:

"Tôi mã hóa U + 00E1 là 11100001"

Và nếu tôi là UTF-8 , tôi có thể nói:

"Đến lượt tôi, tôi mã hóa U + 00E1 là 11000011 10100001"

Và mọi người rõ ràng là chúng tôi muốn nói đến nhân vật nào.

Bây giờ đến sự nhầm lẫn thường phát sinh

Đúng là đôi khi mẫu bit của bảng mã, nếu bạn hiểu nó là số nhị phân, giống với điểm mã Unicode của ký tự này.

Ví dụ:

  • ASCII mã hóa a là 1100001, mà bạn có thể hiểu là số thập lục phân 0x61 và điểm mã Unicode của aU + 0061 .
  • Latin-1 mã hóa á là 11100001, mà bạn có thể hiểu là số thập lục phân 0xE1 và điểm mã Unicode của áU + 00E1 .

Tất nhiên, điều này đã được sắp xếp như thế này nhằm mục đích thuận tiện. Nhưng bạn nên nhìn nó như một sự trùng hợp thuần túy . Mẫu bit được sử dụng để biểu diễn một ký tự trong bộ nhớ không bị ràng buộc theo bất kỳ cách nào với điểm mã Unicode của ký tự này.

Thậm chí không ai nói rằng bạn phải giải thích một chuỗi bit như 11100001 là một số nhị phân. Chỉ cần nhìn nó như là chuỗi các bit mà Latin-1 sử dụng để mã hóa ký tự á .

Quay lại câu hỏi của bạn

Mã hóa được sử dụng bởi trình thông dịch Python của bạn là UTF-8 .

Đây là những gì đang xảy ra trong các ví dụ của bạn:

ví dụ 1

Phần sau mã hóa ký tự á trong UTF-8. Điều này dẫn đến chuỗi bit 11000011 10100001, được lưu trong biến a.

>>> a = 'á'

Khi bạn nhìn vào giá trị của a, nội dung của nó 11000011 10100001 được định dạng là số hex 0xC3 0xA1 và xuất ra dưới dạng '\xc3\xa1':

>>> a
'\xc3\xa1'

Ví dụ 2

Phần sau lưu điểm mã Unicode của á, là U + 00E1, trong biến ua(chúng tôi không biết Python sử dụng định dạng dữ liệu nào bên trong để đại diện cho điểm mã U + 00E1 trong bộ nhớ và nó không quan trọng đối với chúng tôi):

>>> ua = u'á'

Khi bạn nhìn vào giá trị của ua, Python cho bạn biết rằng nó chứa điểm mã U + 00E1:

>>> ua
u'\xe1'

Ví dụ 3

Phần sau mã hóa điểm mã Unicode U + 00E1 (đại diện cho ký tự á) bằng UTF-8, dẫn đến mẫu bit 11000011 10100001. Một lần nữa, đối với đầu ra, mẫu bit này được biểu diễn dưới dạng số hex 0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'

Ví dụ 4

Sau đây mã hóa điểm mã Unicode U + 00E1 (đại diện cho ký tự á) bằng chữ Latinh-1, kết quả là mẫu bit 11100001. Đối với đầu ra, mẫu bit này được biểu diễn dưới dạng số hex 0xE1, trùng hợp giống với mẫu ban đầu điểm mã U + 00E1:

>>> ua.encode('latin1')
'\xe1'

Không có mối quan hệ nào giữa đối tượng Unicode uavà bảng mã Latin-1. Rằng điểm mã của á là U + 00E1 và mã hóa Latinh-1 của á là 0xE1 (nếu bạn giải thích mẫu bit của mã hóa là số nhị phân) là một sự trùng hợp thuần túy.


31

Thiết bị đầu cuối của bạn sẽ được định cấu hình thành UTF-8.

Thực tế là acông việc in ấn là một sự tình cờ; bạn đang ghi các byte UTF-8 thô vào thiết bị đầu cuối. alà một giá trị có độ dài hai , chứa hai byte, giá trị hex C3 và A1, trong khi ualà giá trị unicode có độ dài một , chứa mã điểm U + 00E1.

Sự khác biệt về độ dài này là một lý do chính để sử dụng các giá trị Unicode; bạn không thể dễ dàng đo số ký tự văn bản trong một chuỗi byte; các len()của một chuỗi byte cho bạn biết có bao nhiêu byte được sử dụng, không có bao nhiêu nhân vật đã được mã hóa.

Bạn có thể thấy sự khác biệt khi mã hóa giá trị unicode thành các mã hóa đầu ra khác nhau:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

Lưu ý rằng 256 mã điểm đầu tiên của tiêu chuẩn Unicode khớp với tiêu chuẩn Latinh 1, do đó điểm mã U + 00E1 được mã hóa thành điểm số 1 Latinh dưới dạng byte có giá trị hex E1.

Hơn nữa, Python sử dụng mã thoát trong các biểu diễn của chuỗi byte và unicode như nhau, và các điểm mã thấp không thể in được ASCII cũng được biểu diễn bằng \x..các giá trị thoát. Đây là lý do tại sao một chuỗi với một điểm mã giữa 128 và 255 vẻ Unicode chỉ như 1 mã hóa Latinh. Nếu bạn có một chuỗi unicode với điểm mã vượt quá U + 00FF, một chuỗi thoát khác, \u....được sử dụng thay thế, với giá trị hex gồm bốn chữ số.

Có vẻ như bạn vẫn chưa hiểu rõ sự khác biệt giữa Unicode và bảng mã là gì. Vui lòng đọc các bài viết sau trước khi bạn tiếp tục:


Tôi đã chỉnh sửa câu hỏi của mình với thử nghiệm thêm. Tôi đã đọc cho unicode và mã hóa khác nhau trong một thời gian và tôi nghĩ rằng tôi hiểu được lý thuyết, nhưng khi thực sự kiểm tra mã Python tôi không nắm bắt những gì đang xảy ra
Caumons

1
Mã hóa latin-1 khớp với 256 điểm mã đầu tiên của tiêu chuẩn Unicode. Đây là lý do tại sao U + 00E1 mã hóa thành \xe11 tiếng Latinh
Martijn Pieters

2
Đó là khía cạnh quan trọng nhất của Unicode. Nó không phải là một bảng mã . Nó là văn bản. Unicode là một tiêu chuẩn bao gồm nhiều, nhiều hơn nữa, như thông tin về điểm mã là số, hoặc khoảng trắng hoặc các danh mục khác, nên được hiển thị từ trái sang phải hoặc từ phải sang trái, v.v. vv
Martijn Pieters

1
Nó giống như nói Unicode giống như một "Giao diện" và Mã hóa giống như một "Triển khai" thực tế.
Caumons

2
@Varun: bạn phải sử dụng bản dựng hẹp Python 2, sử dụng nội bộ UCS-2 và trình bày sai bất kỳ thứ gì trên U + FFFF là có độ dài hai. Python 3 và một bản dựng UCS-2 (rộng) sẽ cho bạn thấy độ dài thực sự là 1.
Martijn Pieters

2

Khi bạn xác định a là unicode, các ký tự a và á là bằng nhau. Nếu không thì á được tính là hai ký tự. Hãy thử len (a) và len (au). Ngoài ra, bạn có thể cần phải có mã hóa khi bạn làm việc với các môi trường khác. Ví dụ: nếu bạn sử dụng md5, bạn nhận được các giá trị khác nhau cho a và ua

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.