2.6. ข้อความ vs ไบต์

Python มีประเภทลำดับสองประเภทสำหรับข้อมูลอักขระดิบ:

  • str -- ลำดับของโค้ดพอยต์ Unicode ใช้สำหรับข้อความที่มนุษย์อ่านได้ทั้งหมด เช่น พาธไฟล์ ข้อความ log และ JSON payloads

  • bytes -- ลำดับของจำนวนเต็มในช่วง 0 -- 255 ใช้สำหรับข้อมูลไบนารีดิบ เช่น เฟรม UART บัฟเฟอร์ภาพ แพ็กเก็ตเครือข่าย และค่ารีจิสเตอร์

ทั้งสองประเภทไม่สามารถผสมกันได้โดยไม่มีการแปลงอย่างชัดเจน การส่ง str ไปยังเมธอด write ของฮาร์ดแวร์จะเรียก TypeError และในทางกลับกันก็ถูกปฏิเสธเช่นกัน

A str of Unicode codepoints on the left and a bytes sequence of raw octets on the right, with encode and decode arrows between them.

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") -- สำเนาที่เปลี่ยนแปลงได้ของค่า bytes

  • bytearray("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 ขนาดเล็ก การแบ่งส่วนปกติก็เพียงพอแล้ว