2.6. Texto vs bytes¶
O Python tem dois tipos de sequência para dados de caracteres brutos:
str– uma sequência de pontos de código Unicode. Usado para todo texto legível por humanos: caminhos de arquivo, mensagens de log, payloads JSON.bytes– uma sequência de inteiros no intervalo 0 – 255. Usado para dados binários brutos: quadros UART, buffers de imagem, pacotes de rede, valores de registradores.
Eles não podem ser misturados sem uma conversão explícita. Passar um str para um método write de hardware levanta TypeError, e o inverso também é rejeitado.
Um str armazena caracteres Unicode; um bytes armazena octetos brutos. Passar de um para o outro é codificação (str → bytes) e decodificação (bytes → str).¶
2.6.1. literais de bytes¶
Um literal de bytes é um literal semelhante a uma string prefixado com b:
header = b"OMV"
crlf = b"\r\n"
payload = b"\x01\x02\x03"
Apenas caracteres ASCII são permitidos diretamente dentro de um literal de bytes; valores não-ASCII devem ser escritos como escapes hexadecimais \xHH.
2.6.2. Codificação e decodificação¶
str.encode()converte uma string em bytes usando uma codificação nomeada (padrão"utf-8").bytes.decode()faz o inverso.
>>> "hello".encode()
b'hello'
>>> "héllo".encode()
b'h\xc3\xa9llo' # é is two bytes in UTF-8
>>> b"hello".decode()
'hello'
UTF-8 é o padrão e a escolha correta para qualquer coisa que possa conter caracteres não-ASCII. Use "ascii" apenas quando os dados forem garantidamente ASCII puro; dessa forma, um byte não-ASCII perdido levanta UnicodeError em vez de passar silenciosamente.
2.6.3. Indexação e fatiamento¶
Um valor bytes se comporta como uma sequência de inteiros quando indexado, não como uma sequência de strings de um byte:
>>> data = b"abc"
>>> data[0]
97 # the int 97, not 'a'
>>> data[0:1]
b'a' # slicing returns bytes
Um erro comum é comparar data[0] == "a" e se surpreender que o resultado seja False – data[0] é um inteiro, não uma string de um caractere, então os dois valores nunca podem coincidir.
2.6.4. ord e chr – conectando caracteres e inteiros¶
Como a indexação de um bytes retorna um inteiro, mas o restante do programa provavelmente pensa em termos de caracteres, o Python fornece dois built-ins para alternar entre eles:
ord()– recebe uma string de um caractere e retorna seu ponto de código inteiro.chr()– o inverso: dado um inteiro, retorna a string de um caractere correspondente a esse ponto de código.
>>> ord("a")
97
>>> chr(97)
'a'
>>> ord("A"), chr(0x41)
(65, 'A')
Para caracteres ASCII, o ponto de código é igual ao valor do byte, então ord("a") e b"a"[0] resultam ambos em 97. Isso faz com que as comparações de bytes possam ser lidas em termos do caractere que realmente importa:
>>> data = b"abc"
>>> data[0] == ord("a") # instead of the magic number 97
True
E chr() é útil para registro em log ou depuração quando você quer ver a forma imprimível de um byte:
>>> chr(data[0])
'a'
Para caracteres não-ASCII, ord() retorna o ponto de código Unicode, que não é o mesmo que qualquer byte individual da forma codificada; a representação em bytes depende da codificação.
2.6.5. bytearray para buffers mutáveis¶
bytes é imutável – toda “modificação” retorna um novo objeto e deixa o original intacto. Para dados que você pretende modificar, acrescentar ou preencher peça por peça, use bytearray. Ele guarda o mesmo conteúdo que bytes, mas suporta mutação no local:
>>> 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. Criando um bytearray¶
O construtor bytearray aceita várias entradas:
bytearray(8)– um buffer de 8 bytes zerados.bytearray(b"hello")– uma cópia mutável de um valor bytes.bytearray("hello", "utf-8")– um bytearray a partir de uma string, usando a codificação indicada.bytearray([72, 73, 74])– um bytearray a partir de uma sequência de inteiros em 0 – 255 (aqui,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. Modificando um bytearray¶
A atribuição por índice e por fatia funciona exatamente como em uma 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')
Bytes individuais devem ser inteiros em 0 – 255; atribuir qualquer outro tipo levanta TypeError ou ValueError.
A atribuição por fatia pode alterar o comprimento do buffer. Substituir uma fatia por um valor mais longo aumenta o bytearray; substituir por um valor mais curto o reduz. Substituir por b"" exclui a fatia inteira:
>>> 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')
Os métodos bytearray.append() e bytearray.extend() adicionam bytes ao final sem realocar todo o buffer a cada vez:
>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')
2.6.5.3. Lendo de um bytearray¶
A indexação, o fatiamento, a iteração e os métodos de inspeção de bytes (bytes.startswith(), bytes.find(), bytes.strip(), etc.) funcionam todos da mesma forma que em um valor bytes. A indexação retorna um inteiro; o fatiamento retorna outro bytearray:
>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True
2.6.5.4. Convertendo entre bytes e bytearray¶
bytes e bytearray convertem um no outro por meio de seus construtores. Use isso quando uma API exigir uma forma específica:
>>> 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 fatiamento sem cópia¶
Fatiar um bytes ou bytearray normalmente copia os bytes para um novo buffer. memoryview expõe os mesmos bytes sem copiar:
>>> 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'
Uma view sobre um bytearray também é gravável – alterar a view altera o buffer subjacente:
>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')
Recorra a memoryview quando copiar uma fatia for um desperdício – normalmente quando o mesmo buffer grande é passado adiante ou processado em partes. Para o trabalho cotidiano no estilo de strings com bytes pequenos, o fatiamento comum é suficiente.