GeezerVisonThe World Wide Web, restored to 800 pixels of glory. |
| Home|About|Gaming cabinet|Archives|How it works|Starter sites |
GeezerVison is showing zetcode.com — Python bytes - working with the bytes type in Python Python byteslast modified October 30, 2026 Python 3.15
In this article we show how to use the
The
The conversion between the two worlds is explicit. A string is turned into
bytes with
Python also has a mutable counterpart of Creating bytes
There are several ways to create a creating.py
a = b'falcon'
b = bytes(4)
c = bytes([102, 97, 108, 99, 111, 110])
d = bytes('falcon', 'ascii')
e = 'falcon'.encode()
f = bytes.fromhex('66 61 6c 63 6f 6e')
g = bytes(range(65, 71))
for val in (a, b, c, d, e, f, g):
print(val)
print(a == c == d == e == f)
The example creates the same six bytes in several ways. a = b'falcon'
A bytes literal is a string literal with the b = bytes(4)
With an integer argument, c = bytes([102, 97, 108, 99, 111, 110]) An iterable of integers in the range 0..255 gives one byte per value. d = bytes('falcon', 'ascii')
e = 'falcon'.encode()
A string must be encoded. We pass the encoding to the f = bytes.fromhex('66 61 6c 63 6f 6e')
g = bytes(range(65, 71))
The $ python creating.py b'falcon' b'\x00\x00\x00\x00' b'falcon' b'falcon' b'falcon' b'falcon' b'ABCDEF' True Encoding and decodingThe number of bytes needed for a string depends on the encoding. The next example encodes a Russian word in four encodings. encode_decode.py
word = 'фальконет'
print(word)
print(len(word))
data = word.encode('utf-8')
print(data)
print(len(data))
print(data.decode('utf-8'))
for enc in ('utf-8', 'utf-16-le', 'koi8-r', 'cp1251'):
encoded = word.encode(enc)
print(f'{enc:<10} {len(encoded):>2} bytes {encoded.hex(" ")}')
The word фальконет has nine characters. data = word.encode('utf-8')
print(data)
print(len(data))
In UTF-8, each Cyrillic letter takes two bytes, so the encoded word has 18
bytes. When bytes are printed, values outside printable ASCII are shown as
print(data.decode('utf-8'))
Decoding with the same encoding gives the original string back. for enc in ('utf-8', 'utf-16-le', 'koi8-r', 'cp1251'):
encoded = word.encode(enc)
print(f'{enc:<10} {len(encoded):>2} bytes {encoded.hex(" ")}')
UTF-16 also uses two bytes per letter, but different ones. The legacy Cyrillic encodings KOI8-R and Windows-1251 use one byte per letter. The same text produces completely different bytes; the bytes alone do not tell us how to read them back. $ python encode_decode.py фальконет 9 b'\xd1\x84\xd0\xb0\xd0\xbb\xd1\x8c\xd0\xba\xd0\xbe\xd0\xbd\xd0\xb5\xd1\x82' 18 фальконет utf-8 18 bytes d1 84 d0 b0 d0 bb d1 8c d0 ba d0 be d0 bd d0 b5 d1 82 utf-16-le 18 bytes 44 04 30 04 3b 04 4c 04 3a 04 3e 04 3d 04 35 04 42 04 koi8-r 9 bytes c6 c1 cc d8 cb cf ce c5 d4 cp1251 9 bytes f4 e0 eb fc ea ee ed e5 f2 Decoding errors
Decoding fails when the bytes are not valid in the chosen encoding. The
decode_errors.py
data = 'Dážď'.encode('utf-8')
print(data)
try:
data.decode('ascii')
except UnicodeDecodeError as e:
print(e)
print(data.decode('ascii', errors='replace'))
print(data.decode('ascii', errors='ignore'))
print(data.decode('ascii', errors='backslashreplace'))
print(data[:3].decode('utf-8', errors='replace'))
print(data.decode('cp1250'))
The Slovak word Dážď is encoded in UTF-8 and then decoded in several ways. try:
data.decode('ascii')
except UnicodeDecodeError as e:
print(e)
ASCII has only 128 values. The first byte above 127 raises a
print(data.decode('ascii', errors='replace'))
print(data.decode('ascii', errors='ignore'))
print(data.decode('ascii', errors='backslashreplace'))
The print(data[:3].decode('utf-8', errors='replace'))
Slicing bytes can cut a multi-byte character in half. The first three bytes hold D, the two bytes of á, and nothing of ž; here the cut happens to fall between characters. Cutting one byte later would leave half of ž, which would be replaced. print(data.decode('cp1250'))
Decoding with the wrong single-byte encoding never fails, because every byte maps to some character. The result is mojibake: valid but meaningless text. $ python decode_errors.py b'D\xc3\xa1\xc5\xbe\xc4\x8f' 'ascii' codec can't decode byte 0xc3 in position 1: ordinal not in range(128) D������ D D\xc3\xa1\xc5\xbe\xc4\x8f Dá Dážď Indexing and slicing
A single element of a indexing.py
data = b'falcon'
print(data[0])
print(data[-1])
print(data[1:4])
print(data[::-1])
for byte in data[:3]:
print(byte, chr(byte))
print(99 in data)
print(b'lco' in data)
print(list(data))
We access the elements of the print(data[0]) print(data[-1]) print(data[1:4])
Indexing returns 102, the code of the letter f. Slicing returns
for byte in data[:3]:
print(byte, chr(byte))
Iterating over bytes yields integers; print(99 in data) print(b'lco' in data)
The $ python indexing.py 102 110 b'alc' b'noclaf' 102 f 97 a 108 l True True [102, 97, 108, 99, 111, 110] Bytes literals
The representation of bytes uses the literal syntax. Printable ASCII
characters are shown as themselves, a few control characters use escape
sequences such as literals.py
data = bytes([72, 105, 9, 0, 10, 13, 92, 39, 127, 200, 255])
print(data)
print(len(data))
path = rb'C:\new\table'
print(path, len(path))
text = b'''multi
line'''
print(text)
try:
eval("b'château'")
except SyntaxError as e:
print(e.msg)
The example shows how different values are displayed. data = bytes([72, 105, 9, 0, 10, 13, 92, 39, 127, 200, 255]) The values 72 and 105 are the letters H and i. 9, 10, and 13 are tab, newline, and carriage return. 92 is the backslash and 39 the single quote; because the data contain a single quote, Python switches the representation to double quotes. 0, 127, 200, and 255 have no printable form. path = rb'C:\new\table'
The try:
eval("b'château'")
except SyntaxError as e:
print(e.msg)
Non-ASCII characters are not allowed in bytes literals; such text has to be encoded explicitly. $ python literals.py b"Hi\t\x00\n\r\\'\x7f\xc8\xff" 11 b'C:\\new\\table' 12 b'multi\nline' bytes can only contain ASCII literal characters Immutability and bytearray
A immutable.py
data = b'falcon'
try:
data[0] = 70
except TypeError as e:
print(e)
buf = bytearray(data)
buf[0] = ord('F')
buf.extend(b' and hawk')
buf += b'!'
print(buf)
del buf[6:]
print(buf)
frozen = bytes(buf)
print(frozen, type(frozen).__name__)
We try to change a byte and then do the same with a try:
data[0] = 70
except TypeError as e:
print(e)
Item assignment on buf = bytearray(data)
buf[0] = ord('F')
buf.extend(b' and hawk')
buf += b'!'
A del buf[6:] print(buf) frozen = bytes(buf)
We delete a slice and then create an immutable copy with
$ python immutable.py 'bytes' object does not support item assignment bytearray(b'Falcon and hawk!') bytearray(b'Falcon') b'Falcon' bytes bytearray.take_bytes in Python 3.15
A common pattern is to collect data in a take_bytes.py
buf = bytearray()
for i in range(5):
buf += f'line {i}\n'.encode()
print(len(buf))
head = buf.take_bytes(7)
print(head)
print(buf)
rest = buf.take_bytes()
print(type(rest).__name__, len(rest))
print(buf, len(buf))
We build a buffer of five lines and take data out of it. head = buf.take_bytes(7)
With an argument, rest = buf.take_bytes() Without an argument, it takes everything. The buffer is left empty and can be reused. $ python take_bytes.py 35 b'line 0\n' bytearray(b'line 1\nline 2\nline 3\nline 4\n') bytes 28 bytearray(b'') 0 Bytes methodsBytes support most string methods. The arguments must be bytes too; Python never mixes text and binary data implicitly. methods.py
line = b' GET /index.html HTTP/1.1\r\n'
print(line.strip())
print(line.split())
print(line.strip().startswith(b'GET'))
print(line.upper())
print(line.replace(b'1.1', b'2'))
print(line.find(b'/'))
print(b', '.join([b'sky', b'cloud', b'rock']))
print(b'abc'.center(9, b'*'))
print(b'42'.isdigit(), b'12ab'.isalnum())
try:
line.startswith('GET')
except TypeError as e:
print(e)
try:
b'sky' + 'cloud'
except TypeError as e:
print(e)
We parse a line of an HTTP request. print(line.strip()) print(line.split()) print(line.strip().startswith(b'GET'))
The print(line.upper()) print(b'42'.isdigit(), b'12ab'.isalnum())
Case conversion and the try:
line.startswith('GET')
except TypeError as e:
print(e)
try:
b'sky' + 'cloud'
except TypeError as e:
print(e)
Passing a string to a bytes method or concatenating bytes with a string
raises a $ python methods.py b'GET /index.html HTTP/1.1' [b'GET', b'/index.html', b'HTTP/1.1'] True b' GET /INDEX.HTML HTTP/1.1\r\n' b' GET /index.html HTTP/2\r\n' 6 b'sky, cloud, rock' b'***abc***' True True startswith first arg must be bytes or a tuple of bytes, not str can't concat str to bytes Hexadecimal and binary representations
The hex_repr.py
word = 'château'
data = word.encode('utf-8')
print(data)
print(data.hex())
print(data.hex(' '))
print(data.hex(':', 2))
print([f'{b:02x}' for b in data])
print(' '.join(f'{b:08b}' for b in data))
n = int.from_bytes(data)
print(n)
print(bin(n))
back = n.to_bytes((n.bit_length() + 7) // 8)
print(back.decode('utf-8'))
print(bytes.fromhex('63 68 c3 a2 74 65 61 75').decode())
We show the French word château in several representations. print(data.hex())
print(data.hex(' '))
print(data.hex(':', 2))
The print([f'{b:02x}' for b in data])
print(' '.join(f'{b:08b}' for b in data))
Since every element is an integer, we can format each one with the
n = int.from_bytes(data) print(n) print(bin(n)) back = n.to_bytes((n.bit_length() + 7) // 8) The whole sequence can be read as one big integer and converted back. The number of bytes needed is the bit length rounded up to whole bytes. print(bytes.fromhex('63 68 c3 a2 74 65 61 75').decode())
The $ python hex_repr.py b'ch\xc3\xa2teau' 6368c3a274656175 63 68 c3 a2 74 65 61 75 6368:c3a2:7465:6175 ['63', '68', 'c3', 'a2', '74', '65', '61', '75'] 01100011 01101000 11000011 10100010 01110100 01100101 01100001 01110101 7163190309837693301 0b110001101101000110000111010001001110100011001010110000101110101 château château Integers and byte order
The int_bytes.py
n = 1_000_000
big = n.to_bytes(4, 'big')
little = n.to_bytes(4, 'little')
print(big.hex(' '))
print(little.hex(' '))
print(int.from_bytes(big, 'big'))
print(int.from_bytes(little, 'big'))
print((-2).to_bytes(2, signed=True).hex())
print(int.from_bytes(b'\xff\xfe', signed=True))
try:
(300).to_bytes(1)
except OverflowError as e:
print(e)
We encode one million in four bytes. big = n.to_bytes(4, 'big') little = n.to_bytes(4, 'little') Network protocols traditionally use big-endian order; x86 and ARM processors store integers in little-endian order. Since Python 3.11, big-endian is the default. print(int.from_bytes(little, 'big')) Reading bytes with the wrong byte order gives a completely different number. print((-2).to_bytes(2, signed=True).hex()) print(int.from_bytes(b'\xff\xfe', signed=True))
Negative numbers need try:
(300).to_bytes(1)
except OverflowError as e:
print(e)
A value that does not fit in the given length raises an
$ python int_bytes.py 00 0f 42 40 40 42 0f 00 1000000 1078071040 fffe -2 int too big to convert Binary records with struct
The binary_record.py
import struct
fmt = '<H10sf?'
print(struct.calcsize(fmt))
records = [
(1, 'Paris'.encode(), 21.5, True),
(2, 'Bratislava'.encode(), 18.25, False),
]
with open('cities.bin', 'wb') as f:
for rec in records:
f.write(struct.pack(fmt, *rec))
with open('cities.bin', 'rb') as f:
raw = f.read()
print(len(raw))
print(raw[:17].hex(' '))
for rid, name, temp, active in struct.iter_unpack(fmt, raw):
print(rid, name.rstrip(b'\0').decode(), temp, active)
We write fixed-size city records to a binary file and read them back. fmt = '<H10sf?' print(struct.calcsize(fmt))
The format means little-endian ( f.write(struct.pack(fmt, *rec))
The for rid, name, temp, active in struct.iter_unpack(fmt, raw):
print(rid, name.rstrip(b'\0').decode(), temp, active)
The $ python binary_record.py 17 34 01 00 50 61 72 69 73 00 00 00 00 00 00 00 ac 41 01 1 Paris 21.5 True 2 Bratislava 18.25 False Reading binary filesWhen we open a file in binary mode, reading returns bytes and no decoding or newline translation takes place. data.txt
Dnes bol krásny deň. Dážď bubnoval na okenice. The text file has a Windows line ending on the first line and a Unix one on the second. read_binary.py
with open('data.txt', 'rb') as f:
lines = f.readlines()
print(lines)
for line in lines:
print(line.decode('utf-8').rstrip())
with open('data.txt', 'rb') as f:
raw = f.read()
text = raw.decode('utf-8')
print(len(raw), 'bytes')
print(len(text), 'characters')
We read the file in with open('data.txt', 'rb') as f:
lines = f.readlines()
The print(len(raw), 'bytes') print(len(text), 'characters') The file has 53 bytes but 48 characters, because each of the five accented letters takes two bytes in UTF-8. $ python read_binary.py [b'Dnes bol kr\xc3\xa1sny de\xc5\x88.\r\n', b'D\xc3\xa1\xc5\xbe\xc4\x8f bubnoval na okenice.\n'] Dnes bol krásny deň. Dážď bubnoval na okenice. 53 bytes 48 characters File signaturesMany binary formats start with a fixed sequence of bytes called a signature or magic number. Reading the first few bytes is enough to recognize the type of a file regardless of its name. signatures.py
import bz2
import gzip
import sqlite3
import zipfile
with open('data.gz', 'wb') as f:
f.write(gzip.compress(b'hello'))
with open('data.bz2', 'wb') as f:
f.write(bz2.compress(b'hello'))
with zipfile.ZipFile('archive.zip', 'w') as zf:
zf.writestr('a.txt', 'hello')
with sqlite3.connect('test.db') as con:
con.execute('CREATE TABLE t(x)')
con.close()
with open('notes.txt', 'w') as f:
f.write('plain text')
signatures = {
b'\x1f\x8b': 'gzip data',
b'BZh': 'bzip2 data',
b'PK\x03\x04': 'ZIP archive',
b'SQLite format 3\x00': 'SQLite database',
}
def detect(head):
for magic, kind in signatures.items():
if head.startswith(magic):
return kind
return 'unknown'
for name in ('data.gz', 'data.bz2', 'archive.zip', 'test.db', 'notes.txt'):
with open(name, 'rb') as f:
head = f.read(16)
print(f'{name:<12} {head[:8].hex(" "):<24} {detect(head)}')
We create files with the signatures = {
b'\x1f\x8b': 'gzip data',
b'BZh': 'bzip2 data',
b'PK\x03\x04': 'ZIP archive',
b'SQLite format 3\x00': 'SQLite database',
}
Bytes objects are hashable, so they can be dictionary keys. Some signatures are readable ASCII text, others are arbitrary byte values. with open(name, 'rb') as f:
head = f.read(16)
We read only the first 16 bytes and compare them with
$ python signatures.py data.gz 1f 8b 08 00 00 00 00 00 gzip data data.bz2 42 5a 68 39 31 41 59 26 bzip2 data archive.zip 50 4b 03 04 14 00 00 00 ZIP archive test.db 53 51 4c 69 74 65 20 66 SQLite database notes.txt 70 6c 61 69 6e 20 74 65 unknown Bytes from the networkData received over the network are bytes. The HTTP response headers usually tell us which encoding to use for decoding. network.py
import urllib.request
with urllib.request.urlopen('https://example.com') as resp:
body = resp.read()
charset = resp.headers.get_content_charset()
print(type(body).__name__, len(body))
print(charset)
print(body[:15])
text = body.decode(charset or 'utf-8')
print(type(text).__name__, len(text))
print(text[:15])
We download the example.com home page with body = resp.read() charset = resp.headers.get_content_charset()
The text = body.decode(charset or 'utf-8') We decode the body with the announced charset, falling back to UTF-8. The page is pure ASCII, so the number of bytes and characters is the same. $ python network.py bytes 577 utf-8 b'<!doctype html>' str 577 <!doctype html> Comparing and sorting bytesBytes are compared byte by byte by their numeric values. compare.py
words = [b'zebra', b'Zebra', b'apple', b'\xc3\xa1pple', b'Apple']
print(sorted(words))
print(b'abc' < b'abd', b'abc' < b'abcd')
print(b'a' == 'a')
print(b'falcon' == bytearray(b'falcon'))
print(hash(b'sky') == hash(b'sky'))
counts = {b'GET': 3, b'POST': 1}
print(counts[b'GET'])
We sort a list of words and test equality. print(sorted(words)) Uppercase letters (65..90) sort before lowercase ones (97..122), and the UTF-8 encoded á (starting with byte 0xc3) sorts after all ASCII letters. For human-friendly ordering, decode first and sort strings. print(b'a' == 'a') print(b'falcon' == bytearray(b'falcon'))
Bytes never compare equal to strings. A $ python compare.py [b'Apple', b'Zebra', b'apple', b'zebra', b'\xc3\xa1pple'] True True False True True 3 Source
In this article we have worked with the AuthorMy name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming. List all Python tutorials. |
| You are visitor 01402520 — GeezerVison does not store the pages you look up. Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required. |