2.6. ข้อความ vs ไบต์¶
Python มีประเภทลำดับสองประเภทสำหรับข้อมูลอักขระดิบ:
str-- ลำดับของโค้ดพอยต์ Unicode ใช้สำหรับข้อความที่มนุษย์อ่านได้ทั้งหมด เช่น พาธไฟล์ ข้อความ log และ JSON payloadsbytes-- ลำดับของจำนวนเต็มในช่วง 0 -- 255 ใช้สำหรับข้อมูลไบนารีดิบ เช่น เฟรม UART บัฟเฟอร์ภาพ แพ็กเก็ตเครือข่าย และค่ารีจิสเตอร์
ทั้งสองประเภทไม่สามารถผสมกันได้โดยไม่มีการแปลงอย่างชัดเจน การส่ง str ไปยังเมธอด write ของฮาร์ดแวร์จะเรียก TypeError และในทางกลับกันก็ถูกปฏิเสธเช่นกัน
str เก็บอักขระ Unicode ส่วน bytes เก็บออคเต็ตดิบ การแปลงระหว่างกันคือ การเข้ารหัส (str → bytes) และ การถอดรหัส (bytes → str)¶
2.6.1. ลิเทอรัล bytes¶
ลิเทอรัล bytes คือลิเทอรัลที่มีลักษณะคล้ายสตริงโดยมีคำนำหน้า b:
header = b"OMV"
crlf = b"\r\n"
payload = b"\x01\x02\x03"
อนุญาตให้ใช้เฉพาะอักขระ ASCII โดยตรงภายในลิเทอรัล bytes ค่าที่ไม่ใช่ ASCII ต้องเขียนเป็น hex escape \xHH
2.6.2. การเข้ารหัสและการถอดรหัส¶
str.encode()แปลงสตริงเป็น bytes โดยใช้การเข้ารหัสที่ระบุชื่อ (ค่าเริ่มต้นคือ"utf-8").bytes.decode()ทำในทิศทางตรงกันข้าม
>>> "hello".encode()
b'hello'
>>> "héllo".encode()
b'h\xc3\xa9llo' # é is two bytes in UTF-8
>>> b"hello".decode()
'hello'
UTF-8 เป็นค่าเริ่มต้นและเป็นตัวเลือกที่ถูกต้องสำหรับสิ่งที่อาจมีอักขระที่ไม่ใช่ ASCII ใช้ "ascii" เฉพาะเมื่อข้อมูลรับประกันว่าเป็น ASCII ธรรมดา วิธีนี้ทำให้ไบต์ที่ไม่ใช่ ASCII ที่หลุดเข้ามาจะเรียก UnicodeError แทนที่จะผ่านไปโดยไม่มีการแจ้งเตือน
2.6.3. การจัดทำดัชนีและการแบ่งส่วน¶
ค่า bytes จะทำงานเหมือนลำดับของจำนวนเต็มเมื่อจัดทำดัชนี ไม่ใช่ลำดับของสตริงหนึ่งไบต์:
>>> data = b"abc"
>>> data[0]
97 # the int 97, not 'a'
>>> data[0:1]
b'a' # slicing returns bytes
ข้อผิดพลาดที่พบบ่อยคือการเปรียบเทียบ data[0] == "a" แล้วแปลกใจที่ได้ผลลัพธ์เป็น False -- data[0] เป็นจำนวนเต็ม ไม่ใช่สตริงหนึ่งอักขระ ดังนั้นค่าทั้งสองจะไม่มีทางเท่ากันได้
2.6.4. ord และ chr -- การเชื่อมอักขระกับจำนวนเต็ม¶
เนื่องจากการจัดทำดัชนี bytes คืนค่าจำนวนเต็ม แต่ส่วนอื่นของโปรแกรมมักคิดในรูปแบบอักขระ Python จึงมีฟังก์ชันในตัวสองตัวสำหรับการเคลื่อนที่ระหว่างกัน:
ord()-- รับสตริงหนึ่งอักขระและคืนค่าโค้ดพอยต์จำนวนเต็มchr()-- ตรงกันข้าม: รับจำนวนเต็มและคืนค่าสตริงหนึ่งอักขระสำหรับโค้ดพอยต์นั้น
>>> ord("a")
97
>>> chr(97)
'a'
>>> ord("A"), chr(0x41)
(65, 'A')
สำหรับอักขระ ASCII โค้ดพอยต์จะเท่ากับค่าไบต์ ดังนั้น ord("a") และ b"a"[0] ให้ค่า 97 เหมือนกัน ทำให้การเปรียบเทียบไบต์อ่านได้ในรูปแบบของอักขระที่ต้องการ:
>>> data = b"abc"
>>> data[0] == ord("a") # instead of the magic number 97
True
และ chr() มีประโยชน์สำหรับการบันทึกหรือการดีบักเมื่อต้องการดูรูปแบบที่พิมพ์ได้ของไบต์:
>>> chr(data[0])
'a'
สำหรับอักขระที่ไม่ใช่ ASCII ord() คืนค่าโค้ดพอยต์ Unicode ซึ่งไม่ใช่ไบต์เดียวในรูปแบบที่เข้ารหัส การแทนค่าไบต์ขึ้นอยู่กับการเข้ารหัส
2.6.5. bytearray สำหรับบัฟเฟอร์ที่เปลี่ยนแปลงได้¶
bytes เป็นสิ่งที่เปลี่ยนแปลงไม่ได้ -- ทุก "การแก้ไข" จะคืนค่าออบเจกต์ใหม่และทิ้งต้นฉบับไว้เหมือนเดิม สำหรับข้อมูลที่ต้องการแก้ไข เพิ่มเติม หรือเติมทีละส่วน ให้ใช้ bytearray ซึ่งเก็บเนื้อหาเดียวกับ bytes แต่รองรับการเปลี่ยนแปลงในที่เดิม:
>>> s = b"hello"
>>> s[0] = ord("H")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'bytes' object does not support item assignment
>>> s = bytearray(b"hello")
>>> s[0] = ord("H")
>>> s
bytearray(b'Hello')
2.6.5.1. การสร้าง bytearray¶
คอนสตรัคเตอร์ bytearray รับอินพุตหลายรูปแบบ:
bytearray(8)-- บัฟเฟอร์ 8 ไบต์ที่มีค่าเป็นศูนย์bytearray(b"hello")-- สำเนาที่เปลี่ยนแปลงได้ของค่า bytesbytearray("hello", "utf-8")-- bytearray จากสตริงโดยใช้การเข้ารหัสที่ระบุbytearray([72, 73, 74])-- bytearray จากลำดับของจำนวนเต็มในช่วง 0 -- 255 (ในที่นี้คือb"HIJ")
>>> bytearray(4)
bytearray(b'\x00\x00\x00\x00')
>>> bytearray(b"abc")
bytearray(b'abc')
>>> bytearray("café", "utf-8")
bytearray(b'caf\xc3\xa9')
2.6.5.2. การแก้ไข bytearray¶
การกำหนดค่าแบบจัดทำดัชนีและสไลซ์ทำงานเหมือน list:
>>> buf = bytearray(8) # 8 zero bytes
>>> buf[0] = 0xFF # one byte at a time
>>> buf[1:4] = b"ABC" # replace a slice
>>> buf
bytearray(b'\xffABC\x00\x00\x00\x00')
ไบต์แต่ละตัวต้องเป็นจำนวนเต็มในช่วง 0 -- 255 การกำหนดค่าประเภทอื่นจะเรียก TypeError หรือ ValueError
การกำหนดค่าสไลซ์สามารถเปลี่ยนความยาวของบัฟเฟอร์ได้ การแทนที่สไลซ์ด้วยค่าที่ยาวกว่าจะทำให้ bytearray ขยายขึ้น การแทนที่ด้วยค่าที่สั้นกว่าจะทำให้หดลง การแทนที่ด้วย b"" จะลบสไลซ์ทั้งหมด:
>>> buf = bytearray(b"abcdef")
>>> buf[1:3] = b"XYZ" # 2 bytes replaced with 3
>>> buf
bytearray(b'aXYZdef')
>>> buf[1:4] = b"" # delete the inserted run
>>> buf
bytearray(b'adef')
เมธอด bytearray.append() และ bytearray.extend() เพิ่มไบต์ที่ท้ายโดยไม่ต้องจัดสรรบัฟเฟอร์ทั้งหมดใหม่ทุกครั้ง:
>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')
2.6.5.3. การอ่านจาก bytearray¶
การจัดทำดัชนี การแบ่งส่วน การวนซ้ำ และเมธอดตรวจสอบ bytes (bytes.startswith(), bytes.find(), bytes.strip() เป็นต้น) ทำงานเหมือนกับค่า bytes การจัดทำดัชนีคืนค่าจำนวนเต็ม การแบ่งส่วนคืนค่า bytearray อีกตัว:
>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True
2.6.5.4. การแปลงระหว่าง bytes และ bytearray¶
bytes และ bytearray แปลงระหว่างกันด้วยคอนสตรัคเตอร์ของตน ใช้สิ่งนี้เมื่อ API ต้องการรูปแบบเฉพาะ:
>>> ba = bytearray(b"hello")
>>> snapshot = bytes(ba) # immutable copy
>>> ba[0] = ord("H")
>>> ba, snapshot
(bytearray(b'Hello'), b'hello')
2.6.5.5. memoryview สำหรับการแบ่งส่วนแบบไม่คัดลอก¶
การแบ่งส่วน bytes หรือ bytearray ปกติจะคัดลอกไบต์ไปยังบัฟเฟอร์ใหม่ memoryview เปิดเผยไบต์เดิม โดยไม่ คัดลอก:
>>> buf = bytearray(b"OpenMV Cam")
>>> view = memoryview(buf)
>>> view[0:6] # shares storage with buf
<memoryview ...>
>>> bytes(view[0:6]) # materialise as bytes when needed
b'OpenMV'
view เหนือ bytearray ยังสามารถเขียนได้ -- การเปลี่ยนแปลง view จะเปลี่ยนแปลงบัฟเฟอร์ที่อยู่เบื้องหลัง:
>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')
ใช้ memoryview เมื่อการคัดลอกสไลซ์ไม่คุ้มค่า -- โดยทั่วไปเมื่อบัฟเฟอร์ขนาดใหญ่ชิ้นเดียวกันถูกส่งต่อหรือประมวลผลทีละส่วน สำหรับงานสตริงประจำวันบน bytes ขนาดเล็ก การแบ่งส่วนปกติก็เพียงพอแล้ว