กุญแจสำคัญทั้งหมดของปัญหาการเข้ารหัสดังกล่าวคือการเข้าใจว่าโดยหลักการแล้วมีแนวคิดที่แตกต่างกันสองประการของ "สตริง" : (1) สตริงของอักขระและ (2) สตริง / อาร์เรย์ของไบต์. ความแตกต่างนี้ส่วนใหญ่ถูกละเลยมาเป็นเวลานานเนื่องจากความแพร่หลายในอดีตของการเข้ารหัสที่มีอักขระไม่เกิน 256 ตัว (ASCII, Latin-1, Windows-1252, Mac OS Roman, ... ): การเข้ารหัสเหล่านี้จะจับคู่ชุดอักขระทั่วไปกับ ตัวเลขระหว่าง 0 ถึง 255 (เช่นไบต์); การแลกเปลี่ยนไฟล์ที่ค่อนข้าง จำกัด ก่อนการกำเนิดของเว็บทำให้สถานการณ์ของการเข้ารหัสที่เข้ากันไม่ได้นี้สามารถยอมรับได้เนื่องจากโปรแกรมส่วนใหญ่สามารถเพิกเฉยต่อข้อเท็จจริงที่ว่ามีการเข้ารหัสหลายครั้งตราบใดที่พวกเขาสร้างข้อความที่ยังคงอยู่บนระบบปฏิบัติการเดียวกัน: โปรแกรมดังกล่าวจะ ถือว่าข้อความเป็นไบต์ (ผ่านการเข้ารหัสที่ใช้โดยระบบปฏิบัติการ) มุมมองที่ทันสมัยและถูกต้องจะแยกแนวคิดสตริงทั้งสองนี้อย่างเหมาะสมโดยยึดตามสองจุดต่อไปนี้:
ตัวละครส่วนใหญ่ไม่เกี่ยวข้องกับคอมพิวเตอร์ : สามารถวาดบนกระดานชอล์กเป็นต้นเช่นبايثون, 中蟒และ🐍 "อักขระ" สำหรับเครื่องยังรวมถึง "คำแนะนำในการวาด" เช่นการเว้นวรรคการกลับรถคำแนะนำในการกำหนดทิศทางการเขียน (สำหรับภาษาอาหรับ ฯลฯ ) การเน้นเสียง ฯลฯรายการอักขระที่มีขนาดใหญ่มากจะรวมอยู่ในมาตรฐานUnicode ครอบคลุมตัวละครที่รู้จักเกือบทั้งหมด
ในทางกลับกันคอมพิวเตอร์จำเป็นต้องแสดงอักขระนามธรรมไม่ทางใดก็ทางหนึ่ง: สำหรับสิ่งนี้พวกเขาใช้อาร์เรย์ของไบต์ (รวมตัวเลขระหว่าง 0 ถึง 255) เนื่องจากหน่วยความจำของพวกเขามาในหน่วยไบต์ กระบวนการที่จำเป็นที่ตัวละครแปรรูปไบต์จะเรียกว่าการเข้ารหัส ดังนั้นคอมพิวเตอร์จึงต้องมีการเข้ารหัสเพื่อแสดงอักขระ ข้อความใด ๆ ที่อยู่ในคอมพิวเตอร์ของคุณจะถูกเข้ารหัส (จนกว่าจะแสดง) ไม่ว่าจะถูกส่งไปยังเทอร์มินัล (ซึ่งคาดว่าจะมีการเข้ารหัสอักขระในลักษณะเฉพาะ) หรือบันทึกไว้ในไฟล์ เพื่อที่จะแสดงหรือ "เข้าใจ" อย่างถูกต้อง (โดยพูดว่า Python interpreter) สตรีมของไบต์จะถูกถอดรหัสเป็นอักขระ การเข้ารหัสเล็กน้อย(UTF-8, UTF-16, …) ถูกกำหนดโดย Unicode สำหรับรายการอักขระ (ดังนั้น Unicode จึงกำหนดทั้งรายการอักขระและการเข้ารหัสสำหรับอักขระเหล่านี้ - ยังมีที่ที่เราเห็นนิพจน์ "การเข้ารหัส Unicode" เป็น วิธีอ้างถึง UTF-8 ที่แพร่หลาย แต่เป็นคำศัพท์ที่ไม่ถูกต้องเนื่องจาก Unicode มีการเข้ารหัสหลายรายการ )
โดยสรุปคอมพิวเตอร์จำเป็นต้องแทนอักขระภายในด้วยไบต์และดำเนินการผ่านสองการดำเนินการ:
การเข้ารหัส : อักขระ→ไบต์
การถอดรหัส : ไบต์→อักขระ
การเข้ารหัสบางอย่างไม่สามารถเข้ารหัสอักขระทั้งหมด (เช่น ASCII) ในขณะที่ (บางตัว) การเข้ารหัส Unicode อนุญาตให้คุณเข้ารหัสอักขระ Unicode ทั้งหมด การเข้ารหัสนั้นไม่จำเป็นต้องซ้ำกันเนื่องจากอักขระบางตัวสามารถแสดงได้โดยตรงหรือเป็นชุดค่าผสม (เช่นอักขระพื้นฐานและการเน้นเสียง)
โปรดทราบว่าแนวคิดของการขึ้นบรรทัดใหม่ เพิ่มเลเยอร์ของความซับซ้อนเนื่องจากสามารถแสดงด้วยอักขระ (ตัวควบคุม) ที่แตกต่างกันซึ่งขึ้นอยู่กับระบบปฏิบัติการ (นี่คือสาเหตุของโหมดการอ่านไฟล์นิวไลน์สากลของ Python )
ตอนนี้สิ่งที่ฉันเรียกว่า "อักขระ" ข้างต้นคือสิ่งที่ Unicode เรียกว่า "อักขระที่ผู้ใช้รับรู้ " บางครั้งอักขระที่ผู้ใช้รับรู้เพียงตัวเดียวสามารถแสดงใน Unicode ได้โดยการรวมส่วนของอักขระ (อักขระพื้นฐาน, สำเนียง, ... ) ที่พบในดัชนีต่างๆในรายการ Unicode ซึ่งเรียกว่า " จุดรหัส " - จุดรหัสเหล่านี้สามารถรวมเข้าด้วยกันเพื่อสร้าง "คลัสเตอร์ grapheme" ดังนั้น Unicode จึงนำไปสู่แนวคิดที่สามของสตริงซึ่งทำจากลำดับของจุดรหัส Unicode ซึ่งอยู่ระหว่างไบต์และสตริงอักขระและใกล้เคียงกับสตริงหลังมากกว่า ฉันจะเรียกมันว่า " Unicode strings " (เหมือนใน Python 2)
ในขณะที่ Python สามารถพิมพ์สตริงของอักขระ (ที่ผู้ใช้รับรู้) สตริงที่ไม่ใช่ไบต์ของ Python เป็นลำดับของจุดโค้ด Unicodeไม่ใช่อักขระที่ผู้ใช้รับรู้ ค่าจุดรหัสคือค่าที่ใช้ในไวยากรณ์สตริงของ Python \u
และ\U
Unicode ไม่ควรสับสนกับการเข้ารหัสอักขระ (และไม่ต้องแบกรับความสัมพันธ์ใด ๆ กับมัน: จุดรหัส Unicode สามารถเข้ารหัสได้หลายวิธี)
สิ่งนี้มีผลลัพธ์ที่สำคัญ: ความยาวของสตริง Python (Unicode) คือจำนวนจุดรหัสซึ่งไม่ใช่จำนวนอักขระที่ผู้ใช้รับรู้เสมอไปดังนั้นs = "\u1100\u1161\u11a8"; print(s, "len", len(s))
(Python 3) ให้각 len 3
แม้จะs
มีผู้ใช้เพียงคนเดียว (ภาษาเกาหลี) อักขระ (เนื่องจากแสดงด้วยจุดรหัส 3 จุดแม้ว่าจะไม่จำเป็นต้องprint("\uac01")
แสดงก็ตาม) อย่างไรก็ตามในทางปฏิบัติหลายประการความยาวของสตริงคือจำนวนอักขระที่ผู้ใช้รับรู้เนื่องจากโดยทั่วไปแล้วอักขระจำนวนมากจะถูกจัดเก็บโดย Python เป็นจุดรหัส Unicode เดียว
ในPython 2สตริง Unicode เรียกว่า… "Unicode strings" ( unicode
type, literal form u"…"
) ในขณะที่ byte arrays คือ "strings" ( str
พิมพ์ซึ่งอาร์เรย์ของไบต์สามารถสร้างด้วยตัวอักษรสตริงได้"…"
) ในPython 3สตริง Unicode เรียกง่ายๆว่า "strings" ( str
type, literal form "…"
) ในขณะที่ไบต์อาร์เรย์คือ "ไบต์" ( bytes
ชนิด, รูปแบบตามตัวอักษรb"…"
) ด้วยเหตุนี้บางอย่าง"🐍"[0]
จึงให้ผลลัพธ์ที่แตกต่างกันใน Python 2 ( '\xf0'
, a byte) และ Python 3 ( "🐍"
อักขระตัวแรกและตัวเดียว)
ด้วยประเด็นสำคัญบางประการเหล่านี้คุณจะเข้าใจคำถามที่เกี่ยวข้องกับการเข้ารหัสได้มากที่สุด!
โดยปกติเมื่อคุณพิมพ์ u"…"
ไปที่เทอร์มินัลคุณไม่ควรได้รับขยะ: Python รู้การเข้ารหัสของเทอร์มินัลของคุณ ในความเป็นจริงคุณสามารถตรวจสอบการเข้ารหัสที่เทอร์มินัลต้องการ:
% 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
หากอักขระอินพุตของคุณสามารถเข้ารหัสด้วยการเข้ารหัสของเทอร์มินัล Python จะทำเช่นนั้นและจะส่งไบต์ที่เกี่ยวข้องไปยังเทอร์มินัลของคุณโดยไม่บ่น จากนั้นเทอร์มินัลจะพยายามอย่างเต็มที่ในการแสดงอักขระหลังจากถอดรหัสไบต์อินพุต (ที่แย่ที่สุดคือฟอนต์เทอร์มินัลไม่มีอักขระบางตัวและจะพิมพ์ค่าว่างบางประเภทแทน)
หากอักขระอินพุตของคุณไม่สามารถเข้ารหัสด้วยการเข้ารหัสของเทอร์มินัลแสดงว่าไม่มีการกำหนดค่าเทอร์มินัลให้แสดงอักขระเหล่านี้ Python จะบ่น (ใน Python ที่มีUnicodeEncodeError
เนื่องจากไม่สามารถเข้ารหัสสตริงอักขระด้วยวิธีที่เหมาะสมกับเทอร์มินัลของคุณ) ทางออกเดียวที่เป็นไปได้คือการใช้เทอร์มินัลที่สามารถแสดงอักขระ (ไม่ว่าจะโดยการกำหนดค่าเทอร์มินัลเพื่อให้ยอมรับการเข้ารหัสที่สามารถแทนอักขระของคุณหรือโดยใช้โปรแกรมเทอร์มินัลอื่น) นี่เป็นสิ่งสำคัญเมื่อคุณแจกจ่ายโปรแกรมที่สามารถใช้ได้ในสภาพแวดล้อมที่แตกต่างกัน: ข้อความที่คุณพิมพ์ควรแสดงได้ในเทอร์มินัลของผู้ใช้ บางครั้งจึงเป็นการดีที่สุดที่จะยึดติดกับสตริงที่มีเฉพาะอักขระ ASCII
อย่างไรก็ตามเมื่อคุณเปลี่ยนเส้นทางหรือไพพ์เอาต์พุตของโปรแกรมของคุณโดยทั่วไปจะไม่สามารถทราบได้ว่าการเข้ารหัสอินพุตของโปรแกรมรับคืออะไรและโค้ดด้านบนจะส่งคืนการเข้ารหัสเริ่มต้นบางส่วน: ไม่มี (Python 2.7) หรือ 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
อย่างไรก็ตามการเข้ารหัสของ stdin, stdout และ stderr สามารถตั้งค่าผ่านPYTHONIOENCODING
ตัวแปรสภาพแวดล้อมได้หากจำเป็น:
% PYTHONIOENCODING=UTF-8 python2.7 -c "import sys; print sys.stdout.encoding" | cat
UTF-8
หากการพิมพ์ไปยังเครื่องปลายทางไม่ได้ผลลัพธ์ตามที่คุณคาดหวังคุณสามารถตรวจสอบว่าการเข้ารหัส UTF-8 ที่คุณใส่ด้วยตนเองนั้นถูกต้อง เช่นตัวอักษรตัวแรกของคุณ ( \u001A
) ไม่ได้พิมพ์ถ้าฉันไม่ผิด
ที่http://wiki.python.org/moin/PrintFailsคุณจะพบวิธีแก้ปัญหาดังต่อไปนี้สำหรับ 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
สำหรับ Python 3 คุณสามารถตรวจสอบหนึ่งในคำถามที่ถามก่อนหน้านี้ใน StackOverflow