2.6. Texto frente a bytes¶
Python tiene dos tipos de secuencia para datos de caracteres en bruto:
str– una secuencia de puntos de código Unicode. Se usa para todo el texto legible por humanos: rutas de archivo, mensajes de registro, cargas útiles JSON.bytes– una secuencia de enteros en el rango 0 – 255. Se usa para datos binarios en bruto: tramas UART, búferes de imagen, paquetes de red, valores de registro.
No se pueden mezclar sin una conversión explícita. Pasar un str a un método write de hardware lanza TypeError, y lo inverso también se rechaza.
Un str almacena caracteres Unicode; un bytes almacena octetos en bruto. Pasar de uno a otro es codificar (str → bytes) y decodificar (bytes → str).¶
2.6.1. literales de bytes¶
Un literal de bytes es un literal con forma de cadena precedido del prefijo b:
header = b"OMV"
crlf = b"\r\n"
payload = b"\x01\x02\x03"
Dentro de un literal de bytes solo se permiten caracteres ASCII directamente; los valores no ASCII deben escribirse como escapes hexadecimales \xHH.
2.6.2. Codificación y decodificación¶
str.encode()convierte una cadena en bytes usando una codificación con nombre (por defecto"utf-8").bytes.decode()hace lo inverso.
>>> "hello".encode()
b'hello'
>>> "héllo".encode()
b'h\xc3\xa9llo' # é is two bytes in UTF-8
>>> b"hello".decode()
'hello'
UTF-8 es el valor por defecto y la opción correcta para cualquier cosa que pueda contener caracteres no ASCII. Usa "ascii" solo cuando se garantice que los datos son ASCII puro; de esa forma un byte no ASCII descarriado lanza UnicodeError en lugar de pasar inadvertido.
2.6.3. Indexación y segmentación¶
Un valor de bytes se comporta como una secuencia de enteros cuando se indexa, no como una secuencia de cadenas de un byte:
>>> data = b"abc"
>>> data[0]
97 # the int 97, not 'a'
>>> data[0:1]
b'a' # slicing returns bytes
Un error habitual es comparar data[0] == "a" y sorprenderse de que sea False: data[0] es un entero, no una cadena de un solo carácter, así que los dos valores nunca pueden coincidir.
2.6.4. ord y chr – puente entre caracteres y enteros¶
Dado que indexar un bytes devuelve un entero pero el resto del programa probablemente razona en términos de caracteres, Python proporciona dos funciones integradas para moverse entre ambos:
ord()– toma una cadena de un solo carácter y devuelve su punto de código entero.chr()– lo inverso: dado un entero, devuelve la cadena de un solo carácter de ese punto de código.
>>> ord("a")
97
>>> chr(97)
'a'
>>> ord("A"), chr(0x41)
(65, 'A')
Para los caracteres ASCII el punto de código coincide con el valor del byte, por lo que ord("a") y b"a"[0] dan ambos 97. Eso hace que las comparaciones de bytes se lean en términos del carácter que realmente te interesa:
>>> data = b"abc"
>>> data[0] == ord("a") # instead of the magic number 97
True
Y chr() resulta útil para registrar o depurar cuando quieres ver la forma imprimible de un byte:
>>> chr(data[0])
'a'
Para los caracteres no ASCII, ord() devuelve el punto de código Unicode, que no es lo mismo que ningún byte individual de la forma codificada; la representación en bytes depende de la codificación.
2.6.5. bytearray para búferes mutables¶
bytes es inmutable: cada «modificación» devuelve un objeto nuevo y deja el original intacto. Para datos que pretendas modificar, ampliar o rellenar pieza a pieza, usa bytearray. Contiene el mismo contenido que bytes pero admite la mutación en el sitio:
>>> 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. Crear un bytearray¶
El constructor de bytearray acepta varias entradas:
bytearray(8)– un búfer de 8 bytes a cero.bytearray(b"hello")– una copia mutable de un valor de bytes.bytearray("hello", "utf-8")– un bytearray a partir de una cadena, usando la codificación indicada.bytearray([72, 73, 74])– un bytearray a partir de una secuencia de enteros en 0 – 255 (aquí,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. Modificar un bytearray¶
La asignación indexada y por porciones funciona igual que en una 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')
Los bytes individuales deben ser enteros en 0 – 255; asignar cualquier otro tipo lanza TypeError o ValueError.
La asignación por porciones puede cambiar la longitud del búfer. Reemplazar una porción con un valor más largo hace crecer el bytearray; reemplazar con uno más corto lo encoge. Reemplazar con b"" elimina la porción por completo:
>>> 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')
Los métodos bytearray.append() y bytearray.extend() añaden bytes al final sin tener que reasignar todo el búfer cada vez:
>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')
2.6.5.3. Leer de un bytearray¶
La indexación, la segmentación, la iteración y los métodos de inspección de bytes (bytes.startswith(), bytes.find(), bytes.strip(), etc.) funcionan todos igual que en un valor bytes. La indexación devuelve un entero; la segmentación devuelve otro bytearray:
>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True
2.6.5.4. Conversión entre bytes y bytearray¶
bytes y bytearray se convierten entre sí mediante sus constructores. Usa esto cuando una API requiera específicamente una de las dos formas:
>>> 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 para segmentación sin copia¶
Segmentar un bytes o un bytearray normalmente copia los bytes en un búfer nuevo. memoryview expone los mismos bytes sin copiarlos:
>>> 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'
Una vista sobre un bytearray también permite escritura: mutar la vista muta el búfer subyacente:
>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')
Recurre a memoryview cuando copiar una porción sería un desperdicio, normalmente cuando el mismo búfer grande se pasa de un lado a otro o se procesa por partes. Para el trabajo cotidiano con cadenas de bytes pequeñas, la segmentación normal es suficiente.