bytes.hex()
How binary data gets into logs, tests and config files. Pairs exactly with bytes.fromhex, so the round trip is lossless.
Common call
data.hex()
Returns
str like '616263' — lowercase, unseparated, twice the byte count
Replaces
binascii.hexlify(data).decode()
Watch out
no 0x prefix, so int() needs an explicit base of 16
bytes.hex([sep[, bytes_per_sep]])
→ str
Demo
Live evaluation
Try:
Inputs
sstrtext (encoded as utf-8 first)
Output
bytes('abc', 'utf-8').hex()
'616263'
Every byte becomes exactly two lowercase hex digits with nothing between them, so the string is always twice as long as the data. "abc" gives 616263 because a is 0x61. The non-ascii case shows the encoding underneath: one accented character was encoded as two UTF-8 bytes, so it contributes four hex digits rather than two.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| sep | str | no (None) | Single-character separator between groups, e.g. ' ' or ':'. |
| bytes_per_sep | int | no (1) | Bytes per group. Positive groups from the right; negative groups from the left. |
Return value
str — Two lowercase hex digits per byte, with no 0x prefix and no separator unless one is requested.
Common patterns
Show a digest
The standard way to print a hash, though hashlib offers hexdigest directly.
import hashlib hashlib.sha256(data).digest().hex()
Readable dumps with separators
Grouping makes a long dump scannable.
data.hex(' ') # '61 62 63' data.hex(':', 2) # pairs of bytes
Store binary in a text format
Hex survives JSON, YAML and env vars where raw bytes would not.
config["key"] = secret.hex()
Examples
1. ASCII
b'abc'.hex()
Returns
'616263'2. With spaces
b'abc'.hex(' ')
Returns
'61 62 63'3. Grouped
b'abcd'.hex('_', 2)
Returns
'6162_6364'4. Empty
b''.hex()
Returns
''5. Round trip
bytes.fromhex(b'abc'.hex())
Returns
b'abc'6. Non-ascii
'é'.encode().hex()
Returns
'c3a9'Pitfalls
1. No 0x prefix
Unlike the built-in hex(), the output is bare digits. Passing it to int() without a base makes Python read it as decimal — which either raises or silently returns a completely wrong number.
Read as decimal
int(b'\x10'.hex())
10 # should be 16
State the base
int(b'\x10'.hex(), 16)
16
2. Always lowercase
There is no uppercase option. Comparing against an uppercase fixture or a checksum copied from elsewhere fails on case alone.
Case mismatch
b'\xab'.hex() == 'AB'
False
Normalise
b'\xab'.hex().upper() == 'AB'
True
3. It doubles the size
Every byte becomes two characters, so hex is a 100% overhead encoding. For anything large, base64 costs only about 33% more than the raw bytes.
Twice as big
len(data.hex()) == 2 * len(data)
True
Base64 is denser
import base64 base64.b64encode(data)
about 1.33x
4. It is not the built-in hex()
hex() takes a single integer and returns 0x-prefixed text; this method takes a whole buffer and emits two digits per byte. Same name, unrelated outputs.
Different results
hex(255), b'\xff'.hex()
('0xff', 'ff')
Pick deliberately
hex(n) for ints, data.hex() for bytes
clear intent
When to use
Use it
- Logging or displaying binary data readably
- Digests, checksums and keys in text form
- Test fixtures where a bytes literal would be unreadable
- Carrying binary through a text-only channel
Reach for something else
- Large payloads → base64 is far more compact
- You need a Python int literal → the built-in hex()
- The data is text → decode it instead
Notes
Complexity
O(n) — two characters emitted per byte
Return
A new str, exactly twice the byte length when no separator is used
CPython impl
Objects/bytesobject.c :: bytes_hex
Memory
Allocates a string twice the size of the input
Thread-safe
Yes — bytes are immutable
FAQ
bytes.fromhex reverses it exactly, and it tolerates spaces, so output produced with a separator still parses. The round trip is lossless in both directions.
bytes.fromhex(data.hex()) == data # True
History
3.5
hex() added to bytes, bytearray and memoryview.
3.8
The sep and bytes_per_sep arguments added.