Chuỗi python là ascii hay unicode?

Năm 1991 Guido van Rossum phát hành phiên bản đầu tiên của ngôn ngữ lập trình Python. Vào khoảng thời gian đó, thế giới bắt đầu chứng kiến ​​một sự thay đổi lớn trong cách hệ thống máy tính thể hiện ngôn ngữ viết. Việc nội địa hóa Internet làm tăng nhu cầu hỗ trợ các hệ thống chữ viết khác nhau và Tiêu chuẩn Unicode được phát triển để đáp ứng nhu cầu này. Unicode đã định nghĩa một bộ ký tự chung có thể đại diện cho bất kỳ ngôn ngữ viết nào, các ký hiệu không phải chữ và số khác nhau và cuối cùng là biểu tượng cảm xúc 😀. Python không được thiết kế với Unicode, nhưng nó đã phát triển theo hướng hỗ trợ Unicode trong nhiều năm. Thay đổi lớn đã xảy ra khi Python có hỗ trợ tích hợp sẵn cho các chuỗi Unicode – loại




    
    

4 sau này trở thành loại



    
    

5 trong Python 3. Chuỗi Python đã được chứng minh là một cách thuận tiện để làm việc với văn bản trong thời đại Unicode. Hôm nay chúng ta sẽ xem cách họ làm việc đằng sau hậu trường

Ghi chú. Trong bài viết này tôi đang đề cập đến CPython 3. 9. Một số chi tiết triển khai chắc chắn sẽ thay đổi khi CPython phát triển. Tôi sẽ cố gắng theo dõi những thay đổi quan trọng và thêm ghi chú cập nhật

Phạm vi của bài viết này

Bài đăng này không cố gắng đề cập đến tất cả các khía cạnh của mã hóa văn bản liên quan đến Python. Bạn thấy đấy, các nhà thiết kế ngôn ngữ lập trình phải đưa ra một số quyết định mã hóa văn bản vì họ phải trả lời các câu hỏi sau

  • Cách giao tiếp với thế giới bên ngoài [mã hóa các tham số dòng lệnh, biến môi trường, luồng tiêu chuẩn và hệ thống tệp]
  • Cách đọc mã nguồn [bảng mã của tệp nguồn]
  • Cách thể hiện văn bản bên trong [mã hóa chuỗi]

Bài đăng này tập trung vào vấn đề cuối cùng. Nhưng trước khi chúng ta đi sâu vào bên trong chuỗi Python, hãy thảo luận ngắn gọn về vấn đề mã hóa văn bản trên một ví dụ thực tế và làm rõ Unicode thực sự là gì

Bản chất của mã hóa văn bản

Bạn thấy văn bản này dưới dạng một chuỗi ký tự do trình duyệt của bạn kết xuất và hiển thị trên màn hình của bạn. Tôi thấy văn bản này có cùng một dãy ký tự khi tôi nhập vào trình soạn thảo của mình. Để chúng tôi thấy cùng một nội dung, trình duyệt của bạn và trình chỉnh sửa của tôi phải có thể biểu thị cùng một bộ ký tự, nghĩa là họ phải đồng ý về một bộ ký tự. Họ cũng cần chọn một số cách, có thể khác, để thể hiện văn bản bên trong để có thể làm việc với nó. Ví dụ: họ có thể chọn ánh xạ từng ký tự thành một đơn vị bao gồm một hoặc nhiều byte và biểu thị văn bản dưới dạng một chuỗi các đơn vị đó. Ánh xạ như vậy thường được gọi là mã hóa ký tự. Mã hóa ký tự cũng rất quan trọng đối với giao tiếp của chúng tôi. Trình duyệt của bạn và máy chủ web của tôi phải đồng ý về cách mã hóa văn bản thành byte và giải mã văn bản từ byte, vì byte là những gì chúng truyền tải để giao tiếp với nhau

Bộ ký tự mà trình duyệt của bạn và trình soạn thảo của tôi sử dụng là Unicode. Unicode có thể đại diện cho tiếng Anh cũng như bất kỳ ngôn ngữ viết nào khác mà bạn có thể nghĩ đến [文言, Čeština, Ελληνικά, עברית, हिन्दी], 日本語, Português, Русский] và hàng nghìn ký hiệu khác [₤, ⅐, ⅳ . Máy chủ web của tôi gửi văn bản này dưới dạng một phần của trang HTML theo mã hóa UTF-8. Trình duyệt của bạn biết mã hóa nào được sử dụng để mã hóa văn bản vì tiêu đề HTTP




    
    

6 khai báo mã hóa

Content-Type: text/html; charset=utf-8

Ngay cả khi bạn lưu cục bộ trang HTML này, trình duyệt của bạn vẫn có thể phát hiện mã hóa của nó vì mã hóa được chỉ định trong chính HTML




    
    

Điều này có vẻ vô lý với bạn. Làm cách nào trình duyệt có thể giải mã HTML để đọc mã hóa nếu nó chưa biết mã hóa? . Kiểm tra tiêu chuẩn HTML để tìm hiểu thêm về thuật toán mà các trình duyệt sử dụng để xác định mã hóa

Lưu ý rằng tiêu đề HTTP và thẻ meta HTML chỉ định "bộ ký tự", tôi. e. một bộ ký tự. Điều này có vẻ khó hiểu vì UTF-8 không phải là một bộ ký tự. Những gì họ thực sự chỉ định là mã hóa ký tự. Hai thuật ngữ này thường được sử dụng thay thế cho nhau vì mã hóa ký tự thường ngụ ý một bộ ký tự có cùng tên. Ví dụ: mã hóa ký tự ASCII ngụ ý bộ ký tự ASCII. Tiêu chuẩn Unicode sửa lỗi thuật ngữ bằng cách đưa ra các định nghĩa chính xác cho tất cả các thuật ngữ quan trọng. Chúng ta sẽ nghiên cứu chúng, nhưng trước đó, hãy thảo luận về lý do và cách thức dự án Unicode bắt đầu

Con đường đến với Unicode

Trước khi áp dụng Unicode, hầu hết các hệ thống máy tính đều sử dụng mã hóa ký tự ASCII mã hóa một bộ 128 ký tự bằng cách sử dụng mẫu 7 bit để mã hóa từng ký tự. ASCII là đủ để xử lý các văn bản tiếng Anh nhưng đó là về nó. Các mã hóa ký tự khác đã được phát triển để hỗ trợ nhiều ngôn ngữ hơn. Hầu hết trong số họ đã mở rộng ASCII lên 256 ký tự và sử dụng một byte để mã hóa từng ký tự. Ví dụ: tiêu chuẩn ISO 8859 đã xác định một họ gồm 15 mã hóa ký tự như vậy. Trong số đó có

  • Latinh Tây Âu ISO 8859-1 [tiếng Đức, tiếng Pháp, tiếng Bồ Đào Nha, tiếng Ý, v.v. ]
  • Trung Âu ISO 8859-2 [tiếng Ba Lan, tiếng Croatia, tiếng Séc, tiếng Slovak, v.v. ]
  • Latinh/Cyrillic ISO 8859-5 [tiếng Nga, tiếng Serbia, tiếng Ukraina, v.v. ]
  • Tiếng Latinh/Ả Rập ISO 8859-6
  • Tiếng Latinh/Hy Lạp ISO 8859-7

Phần mềm đa ngôn ngữ phải xử lý nhiều bảng mã ký tự khác nhau. Điều này phức tạp rất nhiều. Một vấn đề khác là chọn mã hóa phù hợp để giải mã văn bản. Không làm như vậy dẫn đến một văn bản bị cắt xén được gọi là mojibake. Ví dụ: nếu bạn mã hóa từ tiếng Nga cho từ mojibake "кракозябры" bằng mã hóa KOI-8 và giải mã từ đó bằng ISO 8859-1, bạn sẽ nhận được "ËÒÁËÏÚÑÂÒ"

Các vấn đề với mã hóa ký tự khác nhau không biến mất hoàn toàn. Tuy nhiên, ngày nay việc viết phần mềm đa ngôn ngữ trở nên dễ dàng hơn nhiều. Điều này là do hai sáng kiến ​​độc lập bắt đầu vào cuối những năm 1980. Một là ISO 10646, một tiêu chuẩn quốc tế, và một là Unicode, một dự án được tổ chức bởi một nhóm các công ty phần mềm. Cả hai dự án đều có cùng một mục tiêu. để thay thế hàng trăm mã hóa ký tự xung đột bằng một mã hóa chung duy nhất bao gồm tất cả các ngôn ngữ được sử dụng rộng rãi. Họ nhanh chóng nhận ra rằng có hai bộ ký tự chung khác nhau sẽ không giúp đạt được mục tiêu, vì vậy vào năm 1991, Bộ ký tự được mã hóa chung [UCS] được xác định bởi ISO 10646 và bộ ký tự của Unicode đã được hợp nhất. Ngày nay, các dự án xác định về cơ bản cùng một mô hình mã hóa ký tự. Tuy nhiên, cả hai vẫn tiếp tục tồn tại. Sự khác biệt giữa chúng là Tiêu chuẩn Unicode có phạm vi lớn hơn

Việc gán các ký tự chỉ là một phần nhỏ so với tiêu chuẩn Unicode và các thông số kỹ thuật liên quan của nó cung cấp. Các thông số kỹ thuật cung cấp cho người lập trình các mô tả mở rộng và một lượng lớn dữ liệu về việc xử lý văn bản, bao gồm cả cách

  • chia từ và ngắt dòng
  • sắp xếp văn bản trong các ngôn ngữ khác nhau
  • định dạng số, ngày tháng, thời gian và các yếu tố khác phù hợp với các địa phương khác nhau
  • hiển thị văn bản cho các ngôn ngữ có dạng chữ viết từ phải sang trái, chẳng hạn như tiếng Ả Rập hoặc tiếng Do Thái
  • hiển thị văn bản trong đó dạng viết được phân tách, kết hợp và sắp xếp lại, chẳng hạn như đối với các ngôn ngữ của Nam Á
  • giải quyết các mối lo ngại về bảo mật liên quan đến nhiều ký tự trông giống nhau từ các hệ thống chữ viết trên khắp thế giới

Điều quan trọng nhất mà chúng ta cần hiểu về Unicode là cách nó mã hóa các ký tự

Khái niệm cơ bản về Unicode

Unicode định nghĩa các ký tự là thành phần nhỏ nhất của ngôn ngữ viết có giá trị ngữ nghĩa. Điều này có nghĩa là các đơn vị như dấu phụ được coi là ký tự của chính chúng. Nhiều ký tự Unicode có thể được kết hợp để tạo ra những gì trực quan trông giống như một ký tự đơn lẻ. Các tổ hợp ký tự như vậy được gọi là cụm biểu đồ trong Unicode. Ví dụ: chuỗi "á" là một cụm biểu đồ bao gồm hai ký tự. chữ cái Latinh "a" và dấu sắc "´". Unicode cũng mã hóa một số cụm biểu đồ thành các ký tự riêng biệt, nhưng điều đó chỉ để tương thích với các mã hóa kế thừa. Do kết hợp các ký tự, Unicode có thể đại diện cho tất cả các loại cụm biểu đồ chẳng hạn như "ä́" và đồng thời giữ cho bộ ký tự tương đối đơn giản

Các ký tự Unicode là trừu tượng. Tiêu chuẩn không quan tâm đến hình dạng chính xác của một ký tự khi được hiển thị. Hình dạng, được gọi là glyph, được coi là mối quan tâm của nhà thiết kế phông chữ. Kết nối giữa các ký tự và glyphs có thể khá phức tạp. Nhiều ký tự có thể hợp nhất thành một hình tượng duy nhất. Một ký tự có thể được hiển thị dưới dạng nhiều glyphs. Và cách các ký tự ánh xạ tới glyphs có thể phụ thuộc vào ngữ cảnh. Xem Báo cáo kỹ thuật Unicode #17 để biết ví dụ

Unicode không ánh xạ trực tiếp các ký tự thành byte. Nó thực hiện ánh xạ theo hai bước

  1. Bộ ký tự được mã hóa ánh xạ các ký tự thành các điểm mã
  2. Dạng mã hóa ký tự, chẳng hạn như UTF-8, ánh xạ các điểm mã thành các chuỗi đơn vị mã, trong đó mỗi đơn vị mã là một chuỗi gồm một hoặc nhiều byte

Bộ ký tự được mã hóa Unicode là những gì chúng ta thường muốn nói khi chúng ta nói Unicode. Nó giống như UCS được xác định bởi ISO 10646. Từ "được mã hóa" có nghĩa là nó không thực sự là một tập hợp mà là một ánh xạ. Ánh xạ này gán một điểm mã cho mỗi ký tự trong bộ ký tự. Điểm mã chỉ là một số nguyên trong phạm vi [0, 1114111], được viết là U+0000. U+10FFFF trong ký hiệu thập lục phân Unicode và được gọi là không gian mã. Unicode 13 hiện tại. 0 gán điểm mã cho 143.859 ký tự

Về mặt kỹ thuật, bộ ký tự được mã hóa là một tập hợp các mục. Mỗi mục xác định một ký tự và gán một điểm mã cho ký tự đó bằng cách chỉ định ba phần thông tin

  • giá trị điểm mã
  • tên nhân vật;
  • một hình tượng đại diện

Ví dụ: mục nhập cho chữ "b" trông như thế này. [U+0062, CHỮ NHỎ LATIN B, b]

Tiêu chuẩn cũng chỉ định các thuộc tính ký tự khác nhau, chẳng hạn như ký tự đó là một chữ cái, một chữ số hay một số ký hiệu khác, cho dù nó được viết từ trái sang phải hay từ phải sang trái và đó là chữ hoa, chữ thường hay không. . Tất cả thông tin này được chứa trong Cơ sở dữ liệu ký tự Unicode. Chúng ta có thể truy vấn cơ sở dữ liệu này từ Python bằng mô-đun chuẩn




    
    

7

Nếu chúng tôi mã hóa một số văn bản bằng bộ ký tự được mã hóa, những gì chúng tôi nhận được là một chuỗi các điểm mã. Chuỗi như vậy được gọi là chuỗi Unicode. Đây là mức trừu tượng phù hợp để xử lý văn bản. Tuy nhiên, máy tính không biết gì về các điểm mã, vì vậy các điểm mã phải được mã hóa thành byte. Unicode định nghĩa ba dạng mã hóa ký tự để làm điều đó. UTF-8, UTF-16 và UTF-32. Mỗi cái có khả năng mã hóa toàn bộ không gian mã nhưng có điểm mạnh và điểm yếu riêng

UTF-32 là dạng mã hóa đơn giản nhất. Mỗi điểm mã được đại diện bởi một đơn vị mã 32 bit. Ví dụ: điểm mã U+01F193 được mã hóa thành




    
    

8. Ưu điểm chính của UTF-32, bên cạnh tính đơn giản, là dạng mã hóa có độ rộng cố định, tôi. e. mỗi điểm mã tương ứng với một số đơn vị mã cố định [trong trường hợp này là một]. Điều này cho phép lập chỉ mục điểm mã nhanh. chúng ta có thể truy cập điểm mã thứ n của chuỗi được mã hóa UTF-32 trong thời gian không đổi

Ban đầu, Unicode chỉ xác định một dạng mã hóa đại diện cho mỗi điểm mã bằng một đơn vị mã 16 bit. Có thể mã hóa toàn bộ không gian mã bằng cách sử dụng dạng mã hóa này vì không gian mã nhỏ hơn và bao gồm 2^16 = 65.536 điểm mã. Theo thời gian, những người sử dụng Unicode nhận ra rằng 65.536 điểm mã là không đủ để bao trùm tất cả ngôn ngữ viết và đã mở rộng không gian mã lên 1.114.112 điểm mã. Vấn đề là các điểm mã mới, tạo thành phạm vi U+010000. U+10FFFF, không thể được biểu thị bằng đơn vị mã 16 bit. Unicode đã giải quyết vấn đề này bằng cách mã hóa từng điểm mã mới bằng một cặp đơn vị mã 16 bit, được gọi là cặp thay thế. Hai phạm vi điểm mã chưa được chỉ định được dành riêng để chỉ được sử dụng trong các cặp thay thế. U+D800. U+DBFF cho các phần cao hơn của các cặp thay thế và U+DC00. U+DFFF cho phần dưới của các cặp thay thế. Mỗi phạm vi này bao gồm 1024 điểm mã, vì vậy chúng có thể được sử dụng để mã hóa 1024 × 1024 = 1.048.576 điểm mã. Dạng mã hóa này sử dụng một đơn vị mã 16 bit để mã hóa các điểm mã trong phạm vi U+0000. U+FFFF và hai đơn vị mã 16 bit để mã hóa các điểm mã trong phạm vi U+010000. U+10FFFF được gọi là UTF-16. Phiên bản gốc của nó là một phần của tiêu chuẩn ISO 10646 và được gọi là UCS-2. Sự khác biệt duy nhất giữa UTF-16 và UCS-2 là UCS-2 không hỗ trợ các cặp thay thế và chỉ có khả năng mã hóa các điểm mã trong phạm vi U+0000. U+FFFF được gọi là Mặt phẳng đa ngôn ngữ cơ bản [BMP]. Tiêu chuẩn ISO 10646 cũng xác định dạng mã hóa UCS-4, tương tự như UTF-32

UTF-32 và UTF-16 được sử dụng rộng rãi để biểu diễn các chuỗi Unicode trong các chương trình. Tuy nhiên, chúng không thích hợp cho việc lưu trữ và truyền tải văn bản. Vấn đề đầu tiên là chúng không hiệu quả về không gian. Điều này đặc biệt đúng khi một văn bản bao gồm hầu hết các ký tự ASCII được mã hóa bằng biểu mẫu mã hóa UTF-32. Vấn đề thứ hai là các byte trong một đơn vị mã có thể được sắp xếp theo thứ tự cuối nhỏ hoặc cuối lớn, vì vậy UTF-32 và UTF-16 có hai loại mỗi loại. Điểm mã đặc biệt được gọi là dấu thứ tự byte [BOM] thường được thêm vào phần đầu của văn bản để xác định độ dài. Và việc xử lý BOM đúng cách làm tăng thêm độ phức tạp. Biểu mẫu mã hóa UTF-8 không có những vấn đề này. Nó đại diện cho mỗi điểm mã bằng một chuỗi gồm một, hai, ba hoặc bốn byte. Các bit đầu tiên của byte đầu tiên cho biết độ dài của chuỗi. Các byte khác luôn có dạng




    
    

9 để phân biệt với byte đầu tiên. Bảng sau đây cho thấy trình tự của mỗi độ dài trông như thế nào và phạm vi điểm mã mà chúng mã hóa

Phạm vi Byte 1 Byte 2 Byte 3 Byte 4U+0000. U+007F
$ python2.7
>>> s = '\xe2\x9c\x85'
>>> print[s]

0U+0080. U+07FF
$ python2.7
>>> s = '\xe2\x9c\x85'
>>> print[s]

1



    
    

9U+0800. U+FFFF
$ python2.7
>>> s = '\xe2\x9c\x85'
>>> print[s]

3



    
    

9



    
    

9U+010000. U+10FFFF
$ python2.7
>>> s = '\xe2\x9c\x85'
>>> print[s]

6



    
    

9



    
    

9



    
    

9

Để mã hóa một điểm mã, chúng tôi chọn một mẫu thích hợp từ bảng trên và thay thế xs trong đó bằng biểu diễn nhị phân của một điểm mã. Mẫu phù hợp là mẫu ngắn nhất có khả năng mã hóa điểm mã. Biểu diễn nhị phân của một điểm mã được căn sang bên phải và các x ở đầu được thay thế bằng 0

Lưu ý rằng UTF-8 đại diện cho tất cả các ký tự ASCII chỉ sử dụng một byte, do đó mọi văn bản được mã hóa ASCII cũng là văn bản được mã hóa UTF-8. Tính năng này là một trong những lý do khiến UTF-8 được chấp nhận và trở thành mã hóa thống trị nhất trên web

Phần này sẽ cung cấp cho chúng ta ý tưởng cơ bản về cách thức hoạt động của Unicode. Nếu bạn muốn tìm hiểu thêm về Unicode, tôi thực sự khuyên bạn nên đọc vài chương đầu của Tiêu chuẩn Unicode

Sơ lược về lịch sử chuỗi Python

Cách chuỗi Python hoạt động ngày nay rất khác so với cách chuỗi Python hoạt động khi Python được phát hành lần đầu tiên. Khía cạnh này của ngôn ngữ đã thay đổi đáng kể nhiều lần. Để hiểu rõ hơn tại sao các chuỗi Python hiện đại hoạt động theo cách của chúng, chúng ta hãy xem nhanh quá khứ

Ban đầu, Python có một loại tích hợp để biểu thị các chuỗi – loại




    
    

5. Đó không phải là loại



    
    

5 mà chúng ta biết ngày nay. Các chuỗi trong Python là các chuỗi byte, nghĩa là các chuỗi byte và hoạt động tương tự như cách các đối tượng



    
    

02 hoạt động trong Python 3. Điều này trái ngược với chuỗi Python 3 là chuỗi Unicode

Vì chuỗi byte là chuỗi byte, nên chúng được sử dụng để biểu diễn tất cả các loại dữ liệu. chuỗi ký tự ASCII, văn bản được mã hóa UTF-8 và mảng byte tùy ý. Bản thân các chuỗi byte không chứa bất kỳ thông tin nào về mã hóa. Đó là tùy thuộc vào một chương trình để giải thích các giá trị. Ví dụ: chúng ta có thể đặt văn bản được mã hóa UTF-8 thành chuỗi byte, in nó ra thiết bị xuất chuẩn và xem các ký tự Unicode thực nếu mã hóa đầu cuối là UTF-8

________số 8

Mặc dù chuỗi byte là chuỗi byte, nhưng chúng được gọi là chuỗi vì một lý do. Lý do là Python đã cung cấp các phương thức chuỗi cho các chuỗi byte, chẳng hạn như




    
    

03 và



    
    

04. Hãy suy nghĩ xem phương thức



    
    

04 sẽ làm gì trên một chuỗi byte. Thật vô nghĩa khi lấy một byte và chuyển đổi nó thành một biến thể chữ hoa vì byte không có chữ hoa chữ thường. Nó bắt đầu có ý nghĩa nếu chúng ta giả định rằng chuỗi byte là một văn bản trong một số mã hóa. Đó chính xác là những gì Python đã làm. Mã hóa giả định phụ thuộc vào ngôn ngữ hiện tại. Thông thường, đó là ASCII. Nhưng chúng tôi có thể thay đổi ngôn ngữ để các phương thức chuỗi bắt đầu hoạt động trên văn bản không được mã hóa ASCII




    
    

0

Việc triển khai logic này dựa trên thư viện chuẩn C. Nó hoạt động với mã hóa có chiều rộng cố định 8 bit nhưng không hoạt động với UTF-8 hoặc bất kỳ mã hóa Unicode nào khác. Nói tóm lại, hồi đó Python không có chuỗi Unicode

Sau đó, loại




    
    

4 đã được giới thiệu. Điều này đã xảy ra trước Python 2 khi PEP chưa tồn tại. Sự thay đổi chỉ được mô tả sau đó trong PEP 100. Các phiên bản của



    
    

4 là các chuỗi Unicode thực, nghĩa là các chuỗi điểm mã [hoặc, nếu bạn thích, các chuỗi ký tự Unicode]. Chúng hoạt động giống như những sợi dây mà chúng ta có ngày nay




    
    

8

Python đã sử dụng mã hóa UCS-2 để biểu thị các chuỗi Unicode bên trong. UCS-2 có khả năng mã hóa tất cả các điểm mã được chỉ định tại thời điểm đó. Nhưng sau đó Unicode đã gán các điểm mã đầu tiên bên ngoài Mặt phẳng đa ngôn ngữ cơ bản và UCS-2 không còn có thể mã hóa tất cả các điểm mã. Python đã chuyển từ UCS-2 sang UTF-16. Giờ đây, bất kỳ điểm mã nào bên ngoài Mặt phẳng đa ngôn ngữ cơ bản đều có thể được biểu thị bằng một cặp thay thế. Điều này gây ra một vấn đề khác. Vì UTF-16 là mã hóa có độ rộng thay đổi nên việc lấy điểm mã thứ n của chuỗi yêu cầu quét chuỗi cho đến khi tìm thấy điểm mã đó. Python hỗ trợ lập chỉ mục thành một chuỗi trong thời gian không đổi và không muốn mất điều đó. Vì vậy, điều đã xảy ra là các đối tượng Unicode được coi là chuỗi Unicode thực và trở thành chuỗi đơn vị mã. Điều này đã có hậu quả sau đây




    
    

9

PEP 261 đã cố gắng hồi sinh các chuỗi Unicode thực sự. Nó đã giới thiệu một tùy chọn thời gian biên dịch kích hoạt mã hóa UCS-4. Bây giờ Python có hai bản dựng riêng biệt. bản dựng "hẹp" và bản dựng "rộng". Việc lựa chọn bản dựng ảnh hưởng đến cách các đối tượng Unicode hoạt động. UCS-4 không thể thay thế hoàn toàn UTF-16 do không hiệu quả về không gian, vì vậy cả hai phải cùng tồn tại. Bên trong, đối tượng Unicode được biểu diễn dưới dạng một mảng gồm các phần tử




    
    

08. Loại



    
    

08 được đặt thành



    
    

80 nếu kích thước của



    
    

80 tương thích với bản dựng. Mặt khác, nó được đặt thành



    
    

82 [UTF-16] hoặc



    
    

83 [UCS-4]

Trong khi đó, các nhà phát triển Python tập trung sự chú ý của họ vào một nguồn gây nhầm lẫn khác. sự cùng tồn tại của chuỗi byte và chuỗi Unicode. Có một số vấn đề với điều này. Ví dụ, có thể trộn hai loại

Content-Type: text/html; charset=utf-8
6

Trừ khi nó không phải là

Content-Type: text/html; charset=utf-8
7

Python 3 nổi tiếng. 0 đã đổi tên loại




    
    

4 thành loại



    
    

5 và thay thế loại



    
    

5 cũ bằng loại



    
    

02. Bản chất của thay đổi này được tóm tắt trong ghi chú phát hành

Sự khác biệt lớn nhất với 2. x là bất kỳ nỗ lực nào để trộn văn bản và dữ liệu trong Python 3. 0 tăng




    
    

88, trong khi nếu bạn kết hợp các chuỗi Unicode và 8 bit trong Python 2. x, nó sẽ hoạt động nếu chuỗi 8 bit chỉ chứa byte 7 bit [ASCII], nhưng bạn sẽ nhận được



    
    

89 nếu chuỗi chứa các giá trị không phải ASCII. Hành vi cụ thể về giá trị này đã gây ra nhiều khuôn mặt đáng buồn trong những năm qua

Chuỗi Python đã trở thành chuỗi Python mà chúng ta biết ngày nay với việc phát hành Python 3. 3. PEP 393 đã loại bỏ các bản dựng "hẹp" và "rộng" và giới thiệu biểu diễn chuỗi linh hoạt. Biểu diễn này làm cho các chuỗi Python trở thành các chuỗi Unicode thực sự mà không có ngoại lệ. Bản chất của nó có thể được tóm tắt như sau. Ba mã hóa có chiều rộng cố định khác nhau được sử dụng để biểu diễn các chuỗi. UCS-1, UCS-2 và UCS-4. Mã hóa nào được sử dụng cho một chuỗi nhất định tùy thuộc vào điểm mã lớn nhất trong chuỗi đó

  • Nếu tất cả các điểm mã nằm trong phạm vi U+0000. U+00FF thì UCS-1 được sử dụng. UCS-1 mã hóa các điểm mã trong phạm vi đó bằng một byte và hoàn toàn không mã hóa các điểm mã khác. Nó tương đương với mã hóa Latin-1 [ISO 8859-1]
  • Nếu tất cả các điểm mã nằm trong phạm vi U+0000. U+FFFF và ít nhất một điểm mã nằm trong phạm vi U+0100. U+FFFF thì UCS-2 được sử dụng
  • Cuối cùng, nếu ít nhất một điểm mã nằm trong phạm vi U+010000. U+10FFFF thì UCS-4 được sử dụng

Ngoài ra, CPython phân biệt trường hợp khi một chuỗi chỉ chứa các ký tự ASCII. Các chuỗi như vậy được mã hóa bằng UCS-1 nhưng được lưu trữ theo cách đặc biệt. Cùng xem qua code thực tế để hiểu chi tiết

Gặp gỡ các chuỗi Python hiện đại

CPython sử dụng ba cấu trúc để biểu diễn các chuỗi.




    
    

90,



    
    

91 và



    
    

92. Cái thứ hai mở rộng cái thứ nhất và cái thứ ba mở rộng cái thứ hai




    
    

7

Tại sao chúng ta cần tất cả các cấu trúc này? . Đặc biệt, nó cung cấp một bộ hàm để làm việc với chuỗi. Nhiều chức năng trong số này hiển thị biểu diễn bên trong của chuỗi, vì vậy PEP 393 không thể loại bỏ biểu diễn cũ mà không phá vỡ các phần mở rộng C. Một trong những lý do tại sao biểu diễn chuỗi hiện tại phức tạp hơn bình thường là do CPython tiếp tục cung cấp API cũ. Ví dụ: nó cung cấp hàm




    
    

93 trả về biểu diễn



    
    

94 của một chuỗi

Trước tiên hãy xem cách CPython đại diện cho các chuỗi được tạo bằng API mới. Chúng được gọi là chuỗi "chuẩn". Chúng bao gồm tất cả các chuỗi mà chúng ta tạo khi viết mã Python. Cấu trúc




    
    

90 được sử dụng để biểu diễn các chuỗi chỉ có ASCII. Bộ đệm chứa một chuỗi không phải là một phần của cấu trúc nhưng ngay sau nó. Việc phân bổ được thực hiện ngay lập tức như thế này




    
    

1

Cấu trúc




    
    

91 được sử dụng để đại diện cho tất cả các chuỗi Unicode khác. Bộ đệm được phân bổ theo cùng một cách ngay sau cấu trúc. Chỉ có



    
    

97 là khác và ________ 298 có thể là ________ 299, ________ 360 hoặc ________ 361

Lý do tại sao cả




    
    

90 và



    
    

91 tồn tại là do tối ưu hóa. Thường cần phải có một biểu diễn UTF-8 của một chuỗi. Nếu một chuỗi là một chuỗi chỉ có ASCII, thì CPython có thể chỉ cần trả về dữ liệu được lưu trữ trong bộ đệm. Nhưng nếu không, CPython phải thực hiện chuyển đổi từ mã hóa hiện tại sang UTF-8. Trường
Content-Type: text/html; charset=utf-8
64 của



    
    

91 được sử dụng để lưu trữ biểu diễn UTF-8 được lưu trong bộ nhớ cache. Đại diện này không phải lúc nào cũng được lưu trữ. Hàm API đặc biệt
Content-Type: text/html; charset=utf-8
66 nên được gọi khi cần bộ đệm

Nếu ai đó yêu cầu đại diện




    
    

94 cũ của chuỗi "chuẩn", thì CPython có thể cần thực hiện chuyển đổi. Tương tự như
Content-Type: text/html; charset=utf-8
64, trường
Content-Type: text/html; charset=utf-8
69 của



    
    

90 được sử dụng để lưu trữ biểu diễn



    
    

94 được lưu trong bộ nhớ cache

API cũ cho phép tạo các chuỗi có bộ đệm

Content-Type: text/html; charset=utf-8
72 và điền vào bộ đệm sau đó. Ngày nay, các chuỗi được tạo theo cách này được gọi là chuỗi "di sản". Chúng được đại diện bởi cấu trúc



    
    

92. Ban đầu, họ chỉ có đại diện



    
    

94. Trường
Content-Type: text/html; charset=utf-8
69 được sử dụng để giữ nó. Người dùng API phải gọi hàm
Content-Type: text/html; charset=utf-8
76 trên các chuỗi "cũ" để làm cho chúng hoạt động với API mới. Hàm này lưu trữ biểu diễn chuẩn [USC-1, UCS-2 hoặc UCS-4] của một chuỗi trong trường
Content-Type: text/html; charset=utf-8
77 của



    
    

92

API cũ vẫn được hỗ trợ nhưng không dùng nữa. PEP 623 đưa ra kế hoạch loại bỏ nó trong Python 3. 12

Có lẽ câu hỏi thú vị nhất về biểu diễn chuỗi linh hoạt là làm thế nào để có được nó. Thông thường, một chuỗi được tạo bằng cách giải mã một chuỗi byte bằng cách sử dụng một số mã hóa. Đây là cách trình phân tích cú pháp tạo chuỗi từ chuỗi ký tự. Đây là cách nội dung của tệp trở thành chuỗi. Và đây là điều xảy ra khi chúng ta gọi phương thức

Content-Type: text/html; charset=utf-8
79 của đối tượng



    
    

02. Trong tất cả các trường hợp này, Python sử dụng mã hóa UTF-8 theo mặc định, vì vậy hãy thảo luận về thuật toán giải mã văn bản được mã hóa UTF-8 thành chuỗi Python. Không rõ ràng ngay lập tức cách triển khai thuật toán như vậy vì CPython cần chọn một cấu trúc và mã hóa thích hợp để biểu thị chuỗi [ASCII, UCS-1, UCS-2 hoặc UCS-4] và nó phải giải mã tất cả các điểm mã để thực hiện . Một giải pháp là đọc đầu vào hai lần. lần đầu tiên để xác định điểm mã lớn nhất trong đầu vào và lần thứ hai để chuyển đổi đầu vào từ mã hóa UTF-8 sang mã hóa nội bộ đã chọn. Đây không phải là những gì CPython làm. Nó cố tỏ ra lạc quan và ban đầu tạo một thể hiện của



    
    

90 để biểu diễn chuỗi. Nếu nó gặp một ký tự không phải ASCII khi đọc đầu vào, nó sẽ tạo một thể hiện của



    
    

91, chọn mã hóa nhỏ gọn nhất tiếp theo có khả năng biểu thị ký tự đó và chuyển đổi tiền tố đã được giải mã thành mã hóa mới. Bằng cách này, nó đọc đầu vào một lần nhưng có thể thay đổi biểu diễn bên trong tới ba lần. Thuật toán được thực hiện trong hàm



    
    

73 trong



    
    

74

Còn rất nhiều điều để nói về chuỗi Python. Việc triển khai các phương thức chuỗi, chẳng hạn như




    
    

75 và



    
    

76, là một chủ đề thú vị, nhưng nó có lẽ xứng đáng có một cổng riêng. Một chủ đề đáng thảo luận khác là thực tập chuỗi. Chúng ta sẽ đề cập đến nó khi chúng ta xem cách hoạt động của từ điển Python. Bài đăng này tập trung vào cách CPython triển khai các chuỗi và nó sẽ không đầy đủ nếu chúng ta không thảo luận về các cách thay thế để triển khai các chuỗi trong ngôn ngữ lập trình, vì vậy đó là những gì chúng ta sẽ làm bây giờ

Cách triển khai Python khác đại diện cho chuỗi

Biểu diễn chuỗi linh hoạt khá phức tạp, vì vậy bạn có thể tự hỏi liệu các triển khai Python khác, chẳng hạn như PyPy và MicroPython, có sử dụng nó không. Câu trả lời ngắn gọn là. họ không. Trên thực tế, tôi không biết về bất kỳ ngôn ngữ nào khác, không nói về việc triển khai Python, sử dụng cách tiếp cận của CPython

MicroPython sử dụng UTF-8 để biểu diễn chuỗi. Các chuỗi là các chuỗi Unicode thực giống như trong CPython. Lập chỉ mục điểm mã được hỗ trợ nhưng được triển khai bằng cách quét chuỗi, vì vậy phải mất \[O[n]\] thời gian để truy cập điểm mã thứ n.

PyPy cũng sử dụng UTF-8. Nhưng nó lập chỉ mục điểm mã trong thời gian không đổi. Bí quyết rất đơn giản. Đây là cách bạn có thể làm điều đó. Hãy nghĩ về biểu diễn UTF-8 dưới dạng một chuỗi các khối, mỗi khối [có thể ngoại trừ khối cuối cùng] chứa 64 điểm mã. Tạo một mảng các số nguyên sao cho phần tử thứ i của mảng là vị trí byte bắt đầu của khối thứ i. Sau đó, điểm mã thứ n của một chuỗi có thể được tìm thấy như sau




    
    

0

Thông báo này trên danh sách gửi thư pypy-dev giải thích thuật toán chi tiết hơn

MicroPython và PyPy phải triển khai cùng chuỗi mà CPython triển khai để duy trì khả năng tương thích với nó. Nhưng các ngôn ngữ khác có quan điểm khác nhau về việc chuỗi nên ở vị trí đầu tiên. Đặc biệt thú vị khi xem xét những ngôn ngữ được thiết kế với Unicode. Đây là trọng tâm của phần tiếp theo

Cách chuỗi hoạt động trong các ngôn ngữ khác

C

Dạng nguyên thủy nhất của kiểu dữ liệu chuỗi là một mảng byte. Chuỗi Python 2 là một ví dụ về cách tiếp cận này. Nó xuất phát từ C nơi các chuỗi được biểu diễn dưới dạng mảng của




    
    

77. Thư viện chuẩn C cung cấp một tập hợp các hàm như



    
    

78 và



    
    

79 lấy byte và coi chúng là ký tự trong mã hóa được chỉ định bởi ngôn ngữ hiện tại. Điều này cho phép làm việc với các bảng mã sử dụng một byte cho mỗi ký tự. Để hỗ trợ các mã hóa khác, loại



    
    

80 đã được giới thiệu trong tiêu chuẩn C90. Không giống như



    
    

77,



    
    

80 được đảm bảo đủ lớn để đại diện cho tất cả các ký tự trong bất kỳ mã hóa nào được chỉ định bởi bất kỳ ngôn ngữ được hỗ trợ nào. Ví dụ: nếu một số ngôn ngữ chỉ định mã hóa UTF-8, thì



    
    

80 phải đủ lớn để biểu thị tất cả các điểm mã Unicode. Vấn đề với



    
    

80 là nó phụ thuộc vào nền tảng và chiều rộng của nó có thể nhỏ tới 8 bit. Tiêu chuẩn C11 đã giải quyết vấn đề này và giới thiệu các loại



    
    

15 và



    
    

16 có thể được sử dụng để biểu thị các đơn vị mã UTF-16 và UTF-32 tương ứng theo cách độc lập với nền tảng. Chương 5 của Tiêu chuẩn Unicode thảo luận chi tiết hơn về các kiểu dữ liệu Unicode trong C

Đi

Trong Go, một chuỗi là một lát byte chỉ đọc, tôi. e. một mảng byte cùng với số byte trong mảng. Một chuỗi có thể chứa các byte tùy ý giống như một mảng




    
    

77 trong C và lập chỉ mục thành một chuỗi trả về một byte. Tuy nhiên, Go cung cấp hỗ trợ Unicode tốt. Đầu tiên, mã nguồn của Go luôn là UTF-8. Điều này có nghĩa là các chuỗi ký tự là các chuỗi UTF-8 hợp lệ. Thứ hai, lặp qua một chuỗi với vòng lặp



    
    

18 mang lại các điểm mã Unicode. Có một loại riêng biệt để biểu thị các điểm mã – loại



    
    

19. Thứ ba, thư viện chuẩn cung cấp các hàm để làm việc với Unicode. Ví dụ: chúng ta có thể sử dụng hàm



    
    

00 do gói



    
    

01 cung cấp để kiểm tra xem chuỗi đã cho có phải là chuỗi UTF-8 hợp lệ hay không. Để tìm hiểu thêm về các chuỗi trong cờ vây, hãy xem bài viết xuất sắc này được viết bởi Rob Pike

rỉ sét

Rust cung cấp một số loại chuỗi. Loại chuỗi chính, được gọi là




    
    

5, được sử dụng để biểu thị văn bản được mã hóa UTF-8. Chuỗi là một lát byte không thể chứa byte tùy ý mà chỉ chứa chuỗi UTF-8 hợp lệ. Cố gắng tạo một chuỗi từ một chuỗi byte không phải là chuỗi UTF-8 hợp lệ dẫn đến lỗi. Lập chỉ mục thành chuỗi theo số nguyên không được hỗ trợ. Các tài liệu đưa ra một lý do cho điều đó

Lập chỉ mục nhằm mục đích hoạt động liên tục, nhưng mã hóa UTF-8 không cho phép chúng tôi thực hiện việc này. Hơn nữa, không rõ loại chỉ mục nào sẽ trả về. một byte, một điểm mã hoặc một cụm grapheme. Các phương thức




    
    

02 và



    
    

04 lần lượt trả về các trình vòng lặp trong hai phương thức đầu tiên

Lặp lại là cách để truy cập các điểm mã. Tuy nhiên, có thể lập chỉ mục thành chuỗi theo phạm vi, chẳng hạn như




    
    

05. Thao tác này trả về một chuỗi con bao gồm các byte trong phạm vi đã chỉ định. Nếu chuỗi con không phải là chuỗi UTF-8 hợp lệ, chương trình sẽ bị sập. Luôn có thể truy cập các byte riêng lẻ của một chuỗi bằng cách chuyển đổi nó thành một lát byte trước. Để tìm hiểu thêm về các chuỗi trong Rust, hãy xem Chương 8 của cuốn sách Ngôn ngữ lập trình Rust

Nhanh

Swift có cách tiếp cận triệt để nhất khi hỗ trợ Unicode. Một chuỗi trong Swift là một chuỗi các cụm biểu đồ Unicode, tức là một chuỗi các ký tự mà con người nhận biết được. Thuộc tính




    
    

06 trả về số cụm grapheme




    
    

1

Và lặp qua một chuỗi tạo ra các cụm grapheme




    
    

2

Để thực hiện hành vi như vậy, một ngôn ngữ phải có khả năng phát hiện ranh giới của các cụm grapheme. Phụ lục tiêu chuẩn Unicode #29 mô tả cách thực hiện điều đó theo thuật toán

Bên trong, một chuỗi được lưu trữ trong bảng mã UTF-8. Lập chỉ mục thành chuỗi theo số nguyên không được hỗ trợ. Tuy nhiên, có một API cho phép truy cập các cụm grapheme theo chỉ số




    
    

3

Có vẻ cố tình vụng về để nhắc nhở các lập trình viên về sự tốn kém của hoạt động. Để tìm hiểu thêm về chuỗi trong Swift, hãy xem Hướng dẫn ngôn ngữ

Sự kết luận

Trong thế giới lập trình hiện đại, từ "chuỗi" có nghĩa là dữ liệu Unicode. Các lập trình viên nên biết cách thức hoạt động của Unicode và các nhà thiết kế ngôn ngữ nên cung cấp sự trừu tượng phù hợp để xử lý nó. Chuỗi Python là chuỗi các điểm mã Unicode. Biểu diễn chuỗi linh hoạt cho phép lập chỉ mục thành chuỗi trong thời gian không đổi, đồng thời, cố gắng giữ cho chuỗi tương đối nhỏ gọn. Cách tiếp cận này dường như hoạt động tốt với Python vì việc truy cập các phần tử của chuỗi rất dễ dàng và trong hầu hết các trường hợp, các lập trình viên thậm chí không nghĩ liệu các phần tử đó phải là ký tự hay cụm biểu đồ. Các ngôn ngữ hiện đại, chẳng hạn như Go, Rust và Swift, đã đặt câu hỏi liệu việc lập chỉ mục thành một chuỗi có quan trọng hay không. Họ cho chúng tôi ý tưởng về cách tiếp cận tốt nhất để triển khai các chuỗi có thể trông như thế nào. biểu diễn các chuỗi bên trong dưới dạng chuỗi UTF-8 và cung cấp một tập hợp các trình vòng lặp mang lại byte, đơn vị mã, điểm mã và cụm biểu đồ. Python tiến hóa. Nó sẽ hấp dẫn đối với phương pháp này trong tương lai?

Việc triển khai các loại tích hợp là một chủ đề hấp dẫn. Thật thú vị và hữu ích khi biết những thứ bạn thường xuyên giải quyết thực sự hoạt động như thế nào. Điều này đặc biệt đúng với từ điển Python. Chúng không chỉ được sử dụng rộng rãi bởi các lập trình viên mà còn là nền tảng cho các tính năng quan trọng của ngôn ngữ. Lần tới chúng ta sẽ xem chúng hoạt động như thế nào


Nếu bạn có bất kỳ câu hỏi, nhận xét hoặc đề xuất nào, vui lòng liên hệ với tôi theo địa chỉ victor@tenthousandmeters. com

Các chuỗi Python có phải là ASCII không?

Mô-đun chuỗi Python tích hợp bao gồm một số hằng số phân loại văn bản ASCII .

Unicode có giống như chuỗi trong Python không?

Python hỗ trợ kiểu chuỗi và kiểu unicode . Một chuỗi là một chuỗi các ký tự trong khi unicode là một chuỗi "con trỏ".

Mã hóa chuỗi Python là gì?

Theo mặc định, Python sử dụng mã hóa utf-8 .

Các chuỗi Python Unicode có được mặc định không?

Sự khác biệt giữa byte và chuỗi Unicode rất quan trọng vì chuỗi trong Python mặc định là Unicode . Tuy nhiên, phần cứng bên ngoài như Arduino, máy hiện sóng và vôn kế truyền các ký tự dưới dạng byte.

Chủ Đề