2.6. Texto vs bytes¶
Python tem dois tipos de sequência para dados de carácter em bruto:
str– uma sequência de pontos de código Unicode. Usada para todo o texto legível por humanos: caminhos de ficheiros, mensagens de registo, payloads JSON.bytes– uma sequência de inteiros no intervalo 0 – 255. Usada para dados binários em bruto: tramas UART, buffers de imagem, pacotes de rede, valores de registos.
Não podem ser misturados sem uma conversão explícita. Passar uma str a um método de hardware write lança TypeError, e o inverso também é rejeitado.
Uma str armazena caracteres Unicode; uma bytes armazena octetos em bruto. Passar de uma para a outra é codificação (str → bytes) e descodificação (bytes → str).¶
2.6.1. Literais bytes¶
Um literal 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 bytes; valores não-ASCII têm de ser escritos como escapes hexadecimais \xHH.
2.6.2. Codificação e descodificação¶
str.encode()converte uma string em bytes usando uma codificação com nome (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 certa para tudo o que possa conter caracteres não-ASCII. Use "ascii" apenas quando os dados são garantidamente ASCII puro; assim um byte não-ASCII inesperado lança UnicodeError em vez de passar silenciosamente.
2.6.3. Indexação e fatiamento¶
Um valor bytes comporta-se 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 ficar surpreendido por ser False – data[0] é um inteiro, não uma string de um carácter, pelo que os dois valores nunca podem ser iguais.
2.6.4. ord e chr – ligação entre caracteres e inteiros¶
Como a indexação de bytes devolve um inteiro mas o resto do programa provavelmente pensa em termos de caracteres, Python disponibiliza duas funções internas para transitar entre eles:
ord()– recebe uma string de um único carácter e devolve o seu ponto de código inteiro.chr()– o inverso: dado um inteiro, devolve a string de um único carácter para 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, pelo que ord("a") e b"a"[0] dão ambos 97. Isso permite que as comparações de bytes sejam lidas em termos do carácter que realmente nos interessa:
>>> data = b"abc"
>>> data[0] == ord("a") # instead of the magic number 97
True
E chr() é útil para registos ou depuração quando se pretende ver a forma imprimível de um byte:
>>> chr(data[0])
'a'
Para caracteres não-ASCII, ord() devolve o ponto de código Unicode, que não é o mesmo que qualquer byte individual na forma codificada; a representação em bytes depende da codificação.
2.6.5. bytearray para buffers mutáveis¶
bytes é imutável – cada «modificação» devolve um novo objeto e deixa o original intacto. Para dados que pretende modificar, acrescentar, ou preencher parte a parte, use bytearray. Contém 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. Criar um bytearray¶
O construtor bytearray aceita várias entradas:
bytearray(8)– um buffer de 8 bytes a zero.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. Modificar um bytearray¶
A atribuição por índice e por slice funciona como numa 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')
Os bytes individuais têm de ser inteiros em 0 – 255; atribuir qualquer outro tipo lança TypeError ou ValueError.
A atribuição por slice pode alterar o comprimento do buffer. Substituir um slice por um valor mais longo aumenta o bytearray; substituir por um valor mais curto reduz-o. Substituir por b"" elimina o slice inteiramente:
>>> 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 no final sem realocar o buffer inteiro a cada vez:
>>> buf = bytearray()
>>> buf.append(0x01)
>>> buf.extend(b"abc")
>>> buf
bytearray(b'\x01abc')
2.6.5.3. Leitura a partir 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 num valor bytes. A indexação devolve um inteiro; o fatiamento devolve outro bytearray:
>>> buf = bytearray(b"OpenMV")
>>> buf[0]
79
>>> buf[0:4]
bytearray(b'Open')
>>> buf.startswith(b"Open")
True
2.6.5.4. Conversão entre bytes e bytearray¶
bytes e bytearray convertem-se mutuamente com os seus construtores. Use isto quando uma API exige especificamente uma das 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 fatiamento sem cópia¶
Fatiar uma 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 vista sobre um bytearray é também gravável – mutar a vista muta o buffer subjacente:
>>> view[0] = ord("o")
>>> buf
bytearray(b'openMV Cam')
Recorra a memoryview quando copiar um slice seria dispendioso – tipicamente quando o mesmo buffer grande é passado entre funções ou processado em partes. Para trabalho quotidiano no estilo de strings com bytes pequenos, o fatiamento simples é suficiente.