Python str เทียบกับประเภท Unicode


103

เมื่อทำงานกับ Python 2.7 ฉันสงสัยว่ามีประโยชน์อะไรบ้างในการใช้ประเภทunicodeแทนที่จะstrเป็นเพราะทั้งคู่ดูเหมือนจะสามารถถือสตริง Unicode ได้ มีเหตุผลพิเศษนอกเหนือจากความสามารถในการตั้งรหัส Unicode ในunicodeสตริงโดยใช้อักขระหลีก\หรือไม่:

การใช้งานโมดูลด้วย:

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

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

ผลลัพธ์ใน: á, á

แก้ไข:

การทดสอบเพิ่มเติมโดยใช้ Python shell:

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

ดังนั้นunicodeดูเหมือนว่าสตริงจะถูกเข้ารหัสโดยใช้latin1แทนutf-8และสตริงดิบถูกเข้ารหัสโดยใช้utf-8? ตอนนี้ฉันยิ่งสับสน! : ส


ไม่มีการเข้ารหัสเพราะunicodeมันเป็นเพียงนามธรรมของอักขระ Unicode unicodeสามารถแปลงเป็นstrด้วยการเข้ารหัสบางอย่าง (เช่นutf-8)
บิน

คำตอบ:


179

unicodeจะหมายถึงการจัดการกับข้อความ ข้อความเป็นลำดับของจุดรหัสซึ่งอาจจะมีขนาดใหญ่กว่าไบต์เดียว ข้อความที่สามารถเข้ารหัสในการเข้ารหัสที่เฉพาะเจาะจงเพื่อแสดงข้อความเป็นไบต์ดิบ (เช่นutf-8, latin-1... )

โปรดทราบunicode ว่าไม่ได้เข้ารหัส ! การแสดงภายในที่ใช้โดย python เป็นรายละเอียดการนำไปใช้งานและคุณไม่ควรสนใจมันตราบเท่าที่สามารถแสดงจุดโค้ดที่คุณต้องการได้

ในทางตรงกันข้ามstrในหลาม 2 เป็นลำดับธรรมดาของไบต์ ไม่ได้แสดงถึงข้อความ!

คุณอาจจะคิดว่าเป็นตัวแทนทั่วไปของข้อความบางอย่างที่สามารถเข้ารหัสในรูปแบบที่แตกต่างกันจำนวนมากในลำดับของข้อมูลไบนารีผ่านทางตัวแทนunicodestr

หมายเหตุ: ใน Python 3 unicodeถูกเปลี่ยนชื่อเป็นstrและมีbytesประเภทใหม่สำหรับลำดับไบต์ธรรมดา

ความแตกต่างบางประการที่คุณเห็น:

>>> 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

โปรดทราบว่าการใช้strคุณมีตัวควบคุมระดับล่างบนไบต์เดียวของการแทนการเข้ารหัสเฉพาะในขณะที่ใช้unicodeคุณสามารถควบคุมได้ที่ระดับจุดโค้ดเท่านั้น ตัวอย่างเช่นคุณสามารถทำได้:

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

UTF-8 ที่ถูกต้องคืออะไรก่อนหน้านี้ไม่ใช่อีกต่อไป การใช้สตริง Unicode ทำให้คุณไม่สามารถดำเนินการในลักษณะที่สตริงผลลัพธ์ไม่ใช่ข้อความ Unicode ที่ถูกต้อง คุณสามารถลบจุดรหัสแทนที่จุดรหัสด้วยจุดรหัสอื่นเป็นต้น แต่คุณไม่สามารถยุ่งกับการแสดงภายในได้


4
ขอบคุณมากสำหรับคำตอบของคุณมันช่วยได้มาก! ส่วนที่ชัดเจนที่สุดสำหรับฉันคือ: "unicode ไม่ได้เข้ารหัส! การแสดงภายในที่ใช้โดย python เป็นรายละเอียดการใช้งานและคุณไม่ควรสนใจเกี่ยวกับมัน [... ]" ดังนั้นเมื่อทำการทำให้เป็นอนุกรมunicodeวัตถุฉันเดาว่าอันดับแรกเราต้องทำให้encode()พวกเขาอยู่ในรูปแบบการเข้ารหัสที่เหมาะสมอย่างชัดเจนเนื่องจากเราไม่รู้ว่าจะใช้วัตถุใดภายในเพื่อแสดงunicodeค่า
Caumons

10
ใช่. เมื่อคุณต้องการที่จะบันทึกข้อความบางส่วน (เช่นไฟล์) คุณจะต้องเป็นตัวแทนของมันด้วยไบต์คือคุณจะต้องเข้ารหัสมัน เมื่อดึงเนื้อหาคุณควรทราบการเข้ารหัสที่ใช้เพื่อที่จะสามารถถอดรหัสไบต์เป็นunicodeวัตถุได้
Bakuriu

ฉันขอโทษ แต่ข้อความที่unicodeไม่ได้เข้ารหัสนั้นผิด UTF-16 / UCS-2 และ UTF-32 / UCS-4 ก็มีการเข้ารหัสเช่นกัน ... และอาจมีการสร้างสิ่งเหล่านี้เพิ่มเติมในอนาคต ประเด็นเป็นเพียงเพราะคุณไม่ควรสนใจรายละเอียดการใช้งาน (และคุณไม่ควร!) แต่ก็ไม่ได้หมายความunicodeว่าไม่ได้เข้ารหัส มันเป็นแน่นอน ไม่ว่าจะ.decode()เป็นเรื่องที่แตกต่างกันทั้งหมด
0xC0000022L

1
@ 0xC0000022L บางทีประโยคมันอาจจะไม่ชัดเจน ควรพูดว่า: การunicodeแสดงภายในของวัตถุอาจเป็นอะไรก็ได้ที่ต้องการรวมทั้งการแสดงที่ไม่ได้มาตรฐาน โดยเฉพาะอย่างยิ่งใน python3 + unicode จะใช้การแสดงภายในที่ไม่ได้มาตรฐานซึ่งจะเปลี่ยนแปลงไปตามข้อมูลที่มีอยู่ เช่นนี้มันไม่ได้เป็นเข้ารหัสมาตรฐาน Unicode เป็นมาตรฐานข้อความกำหนดเฉพาะจุดรหัสซึ่งเป็นตัวแทนของข้อความแบบนามธรรมมีหลายวิธีในการเข้ารหัส Unicode ในหน่วยความจำรวมถึง utf-X มาตรฐานเป็นต้น Python ใช้วิธีของตัวเองเพื่อประสิทธิภาพ
Bakuriu

1
@ 0xC0000022L นอกจากนี้ข้อเท็จจริงที่ว่า UTF-16 เป็นการเข้ารหัสไม่มีส่วนเกี่ยวข้องกับunicodeวัตถุของ CPython เนื่องจากไม่ใช้ UTF-16 หรือ UTF-32 encodeโดยจะใช้เป็นตัวแทนเฉพาะกิจและถ้าคุณต้องการการเข้ารหัสข้อมูลลงในไบต์ที่เกิดขึ้นจริงที่คุณต้องใช้ นอกจากนี้: ภาษาไม่ได้กำหนดวิธีunicodeการใช้งานดังนั้นเวอร์ชันหรือการใช้งาน python ที่แตกต่างกันจึงสามารถ (และมี ) การแสดงภายในที่แตกต่างกัน
Bakuriu

38

Unicode และการเข้ารหัสเป็นสิ่งที่แตกต่างกันโดยสิ้นเชิงไม่เกี่ยวข้องกัน

Unicode

กำหนดรหัสตัวเลขให้กับอักขระแต่ละตัว:

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

ดังนั้น Unicode จึงกำหนดหมายเลข 0x41 ให้กับ A, 0xE1 ถึงáและ 0x414 ให้กับД

แม้แต่ลูกศรเล็ก ๆ →ที่ฉันใช้ก็มีหมายเลข Unicode นั่นคือ 0x2192 และแม้แต่อิโมจิก็มีหมายเลข Unicode ด้วย😂คือ 0x1F602

คุณสามารถค้นหาหมายเลข Unicode ของอักขระทั้งหมดในตารางนี้ โดยเฉพาะอย่างยิ่งคุณสามารถหาตัวละครทั้งสามดังกล่าวข้างต้นเป็นครั้งแรกที่นี่ที่ลูกศรที่นี่และอีโมจิที่นี่

ตัวเลขเหล่านี้ได้รับมอบหมายให้ทุกตัวอักษรโดย Unicode จะเรียกว่าจุดรหัส

จุดประสงค์ของทั้งหมดนี้คือเพื่อให้วิธีการอ้างถึงตัวละครแต่ละตัวอย่างชัดเจน ตัวอย่างเช่นถ้าผมพูดเกี่ยวกับ😂แทนที่จะพูดว่า"คุณรู้ว่านี้อิโมจิหัวเราะด้วยน้ำตา"ผมสามารถเพียงแค่บอกว่ารหัส Unicode จุด 0x1F602 ง่ายกว่าใช่มั้ย?

โปรดทราบว่าโดยทั่วไปจุดรหัส Unicode จะถูกจัดรูปแบบด้วยU+เลขนำหน้าจากนั้นค่าตัวเลขฐานสิบหกจะมีตัวเลขอย่างน้อย 4 หลัก ตัวอย่างข้างต้นจะเป็น U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602

จุดรหัส Unicode มีตั้งแต่ U + 0000 ถึง U + 10FFFF นั่นคือ 1,114,112 ตัวเลข 2048 ของตัวเลขเหล่านี้ใช้สำหรับตัวแทนดังนั้นจึงยังคงมี 1,112,064 ซึ่งหมายความว่า Unicode สามารถกำหนด ID เฉพาะ (จุดรหัส) ให้กับอักขระที่แตกต่างกัน 1,112,064 ตัว ยังไม่ได้กำหนดจุดรหัสเหล่านี้ทั้งหมดให้กับอักขระและ Unicode จะขยายออกไปเรื่อย ๆ (เช่นเมื่อมีการนำอิโมจิใหม่มาใช้)

สิ่งสำคัญที่ต้องจำไว้คือ Unicode ทั้งหมดคือการกำหนด ID ตัวเลขที่เรียกว่าจุดรหัสให้กับอักขระแต่ละตัวเพื่อการอ้างอิงที่ง่ายและไม่คลุมเครือ

การเข้ารหัส

แมปอักขระกับรูปแบบบิต

รูปแบบบิตเหล่านี้ใช้เพื่อแสดงอักขระในหน่วยความจำคอมพิวเตอร์หรือบนดิสก์

มีการเข้ารหัสที่แตกต่างกันมากมายซึ่งครอบคลุมชุดย่อยของอักขระต่างๆ ในโลกที่ใช้ภาษาอังกฤษการเข้ารหัสที่พบบ่อยที่สุดมีดังต่อไปนี้:

ASCII

แมปอักขระ 128 ตัว (จุดรหัส U + 0000 ถึง U + 007F) เป็นรูปแบบบิตของความยาว 7

ตัวอย่าง:

  • ก→ 1100001 (0x61)

คุณสามารถดูการแมปทั้งหมดในตารางนี้

ISO 8859-1 (aka Latin-1)

จับคู่อักขระ 191 ตัว (โค้ดชี้ U + 0020 ถึง U + 007E และ U + 00A0 ถึง U + 00FF) เป็นรูปแบบบิตที่มีความยาว 8

ตัวอย่าง:

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

คุณสามารถดูการแมปทั้งหมดในตารางนี้

UTF-8

แมปอักขระ 1,112,064 ตัว (จุดรหัส Unicode ที่มีอยู่ทั้งหมด) เป็นรูปแบบบิตที่มีความยาว 8, 16, 24 หรือ 32 บิต (นั่นคือ 1, 2, 3 หรือ 4 ไบต์)

ตัวอย่าง:

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

วิธีที่ตัวละคร UTF-8 ถอดรหัสสตริงบิตอธิบายไว้อย่างดีที่นี่

Unicode และการเข้ารหัส

เมื่อดูจากตัวอย่างข้างต้นจะเห็นได้ชัดเจนว่า Unicode มีประโยชน์อย่างไร

ตัวอย่างเช่นถ้าฉันเป็นภาษาละติน -1และฉันต้องการอธิบายการเข้ารหัสของฉันฉันไม่จำเป็นต้องพูดว่า:

"ฉันเข้ารหัสว่า a ด้วย aigu (หรือที่คุณเรียกแถบที่เพิ่มขึ้นนั้น) เป็น 11100001"

แต่ฉันสามารถพูดได้ว่า:

"ฉันเข้ารหัส U + 00E1 เป็น 11100001"

และถ้าฉันเป็นUTF-8ฉันสามารถพูดได้ว่า:

"ฉันฉันเข้ารหัส U + 00E1 เป็น 11000011 10100001"

และทุกคนก็ชัดเจนอย่างชัดเจนว่าเราหมายถึงตัวละครใด

ตอนนี้ความสับสนที่เกิดขึ้นบ่อยๆ

เป็นเรื่องจริงที่บางครั้งรูปแบบบิตของการเข้ารหัสหากคุณตีความว่าเป็นเลขฐานสองจะเหมือนกับจุดรหัส Unicode ของอักขระนี้

ตัวอย่างเช่น:

  • ASCII encodes เป็น 1100001 ซึ่งคุณสามารถตีความเป็นจำนวนฐานสิบหก0x61และจุดรหัส Unicode ของเป็นU + 0061
  • Latin-1 ถอดรหัสáเป็น 11100001 ซึ่งคุณสามารถตีความเป็นเลขฐานสิบหก0xE1และจุดรหัส Unicode ของáเป็นU + 00E1

แน่นอนว่าสิ่งนี้ถูกจัดเรียงไว้เช่นนี้โดยมีวัตถุประสงค์เพื่อความสะดวก แต่คุณควรมองไปที่มันเป็นเรื่องบังเอิญที่บริสุทธิ์ รูปแบบบิตที่ใช้แทนอักขระในหน่วยความจำจะไม่เชื่อมโยงกับจุดรหัส Unicode ของอักขระนี้

ไม่มีใครบอกว่าคุณต้องตีความสตริงบิตเช่น 11100001 เป็นเลขฐานสอง เพียงแค่มองไปที่มันเป็นลำดับของบิตที่ Latin-1 ใช้การเข้ารหัสอักขระá

กลับไปที่คำถามของคุณ

การเข้ารหัสที่ใช้โดยล่ามหลามของคุณเป็นUTF-8

นี่คือสิ่งที่เกิดขึ้นในตัวอย่างของคุณ:

ตัวอย่าง 1

ต่อไปนี้เข้ารหัสอักขระáใน UTF-8 ผลนี้ในสตริงบิต 11000011 10100001 aซึ่งถูกบันทึกไว้ในตัวแปร

>>> a = 'á'

เมื่อคุณดูค่าของaเนื้อหา 11000011 10100001 จะถูกจัดรูปแบบเป็นเลขฐานสิบหก 0xC3 0xA1 และเอาต์พุตเป็น'\xc3\xa1':

>>> a
'\xc3\xa1'

ตัวอย่าง 2

ต่อไปนี้จะบันทึกจุดรหัส Unicode ของáซึ่งก็คือ U + 00E1 ในตัวแปรua(เราไม่รู้ว่า Python รูปแบบข้อมูลใดใช้เป็นการภายในเพื่อแทนจุดรหัส U + 00E1 ในหน่วยความจำและไม่สำคัญสำหรับเรา):

>>> ua = u'á'

เมื่อคุณดูค่าของuaPython จะบอกคุณว่ามีจุดรหัส U + 00E1:

>>> ua
u'\xe1'

ตัวอย่างที่ 3

ต่อไปนี้เข้ารหัสจุดรหัส Unicode U + 00E1 (แทนอักขระá) ด้วย UTF-8 ซึ่งส่งผลให้เกิดรูปแบบบิต 11000011 10100001 อีกครั้งสำหรับเอาต์พุตรูปแบบบิตนี้จะแสดงเป็นเลขฐานสิบหก 0xC3 0xA1:

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

ตัวอย่างที่ 4

ต่อไปนี้เข้ารหัส Unicode code point U + 00E1 (แทนอักขระá) ด้วย Latin-1 ซึ่งให้ผลลัพธ์เป็นรูปแบบบิต 11100001 สำหรับเอาต์พุตรูปแบบบิตนี้จะแสดงเป็นเลขฐานสิบหก 0xE1 ซึ่งโดยบังเอิญจะเหมือนกับค่าเริ่มต้น จุดรหัส U + 00E1:

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

ไม่มีความสัมพันธ์ระหว่างวัตถุ Unicode uaและการเข้ารหัส Latin-1 จุดรหัสของáคือ U + 00E1 และการเข้ารหัสภาษาละติน -1 ของáคือ 0xE1 (หากคุณตีความรูปแบบบิตของการเข้ารหัสเป็นเลขฐานสอง) เป็นเรื่องบังเอิญอย่างแท้จริง


31

เทอร์มินัลของคุณถูกกำหนดค่าเป็น UTF-8

ความจริงที่ว่าaงานพิมพ์เป็นเรื่องบังเอิญ คุณกำลังเขียนดิบ UTF-8 ไบต์ไปยังเทอร์มินัล aคือค่าของความยาว2ซึ่งมีสองไบต์ค่าฐานสิบหก C3 และ A1 ในขณะที่uaเป็นค่ายูนิโคดของความยาวหนึ่งซึ่งมีจุดรหัส U + 00E1

ความยาวที่แตกต่างกันนี้เป็นเหตุผลสำคัญประการหนึ่งในการใช้ค่า Unicode คุณไม่สามารถวัดจำนวนอักขระข้อความในสตริงไบต์ได้อย่างง่ายดาย len()ของสตริงไบต์จะบอกคุณว่าจำนวนไบต์ถูกนำมาใช้ไม่ว่าตัวละครหลายคนถูกเข้ารหัส

คุณจะเห็นความแตกต่างเมื่อคุณเข้ารหัสค่า Unicode เป็นการเข้ารหัสเอาต์พุตที่แตกต่างกัน:

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

โปรดทราบว่าจุดรหัส 256 จุดแรกของมาตรฐาน Unicode ตรงกับมาตรฐานละติน 1 ดังนั้นจุดรหัส U + 00E1 จึงถูกเข้ารหัสเป็นภาษาละติน 1 เป็นไบต์ที่มีค่าฐานสิบหก E1

นอกจากนี้ Python ยังใช้โค้ด Escape ในการแสดงสตริง Unicode และ byte และจุดโค้ดต่ำที่ไม่สามารถพิมพ์ ASCII จะแสดงโดยใช้\x..ค่า Escape เช่นกัน นี่คือเหตุผลที่สายอักขระ Unicode กับจุดรหัสระหว่าง 128 และ 255 รูปลักษณ์เพียงเช่นละติน 1 การเข้ารหัส หากคุณมีสตริง Unicode ที่มีจุดรหัสเกินกว่า U + 00FF จะมีลำดับการหลีกเลี่ยงที่แตกต่างกัน\u....จะใช้แทนโดยใช้ค่าฐานสิบหกสี่หลัก

ดูเหมือนว่าคุณจะยังไม่เข้าใจอย่างถ่องแท้ว่าความแตกต่างระหว่าง Unicode กับการเข้ารหัสคืออะไร โปรดอ่านบทความต่อไปนี้ก่อนดำเนินการต่อ:


ฉันแก้ไขคำถามของฉันด้วยการทดสอบเพิ่มเติม ฉันอ่าน Unicode และการเข้ารหัสที่แตกต่างกันมาระยะหนึ่งแล้วและฉันคิดว่าฉันเข้าใจทฤษฎี แต่เมื่อทดสอบโค้ด Python จริงฉันไม่จับสิ่งที่เกิดขึ้น
Caumons

1
การเข้ารหัส latin-1 ตรงกับ 256 codepoints แรกของมาตรฐาน Unicode นี่คือเหตุผลที่ U + 00E1 เข้ารหัสเป็น\xe1ภาษาละติน 1
Martijn Pieters

2
นั่นคือสิ่งที่สำคัญที่สุดเพียงประการเดียวสำหรับ Unicode มันไม่ได้เป็นเข้ารหัส มันเป็นข้อความ Unicode เป็นมาตรฐานที่มีข้อมูลอื่น ๆ อีกมากมายเช่นข้อมูลเกี่ยวกับจุดรหัสที่เป็นตัวเลขหรือช่องว่างหรือหมวดหมู่อื่น ๆ ควรแสดงจากซ้ายไปขวาหรือขวาไปซ้าย ฯลฯ เป็นต้น
Martijn Pieters

1
เหมือนกับการบอกว่า Unicode เป็นเหมือน "อินเทอร์เฟซ" และการเข้ารหัสก็เหมือนกับ "การนำไปใช้งาน" ที่แท้จริง
Caumons

2
@Varun: คุณต้องใช้โครงสร้างแคบของ Python 2 ซึ่งใช้ UCS-2 ภายในและแสดงถึงสิ่งที่ผิดพลาดใน U + FFFF เนื่องจากมีความยาวสอง Python 3 และ UCS-2 (กว้าง) จะแสดงให้คุณเห็นความยาวคือ 1 จริงๆ
Martijn Pieters

2

เมื่อคุณกำหนดเป็น Unicode ตัวอักษร a และáจะเท่ากัน มิฉะนั้นจะนับเป็นสองตัวอักษร ลอง len (a) และ len (au) นอกจากนั้นคุณอาจต้องมีการเข้ารหัสเมื่อคุณทำงานกับสภาพแวดล้อมอื่น ๆ ตัวอย่างเช่นถ้าคุณใช้ md5 คุณจะได้รับค่า a และ ua ต่างกัน

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.