Skip to content
Happy Programming Guide
Start learning
Python

Python Byte to String Encoding

How to convert bytes to str and back, what UTF-8 actually is, and how to fix UnicodeDecodeError when a file is not the encoding you assumed.

An open laptop on a wooden desk

To turn bytes into a string in Python: data.decode("utf-8"). To go the other way: text.encode("utf-8"). The confusion is not the syntax but the concept — bytes are raw numbers, strings are characters, and an encoding is the rulebook that maps between them. Get the rulebook wrong and you either get mojibake or a UnicodeDecodeError.

The two types#

Python
text = "café"           # str  - characters
data = b"cafe"          # bytes - raw numbers

print(type(text))       # <class 'str'>
print(type(data))       # <class 'bytes'>
print(len("café"))      # 4 characters
print(len("café".encode("utf-8")))   # 5 bytes

That last pair is the whole idea in two lines. The word has four characters, but the accented e needs two bytes in UTF-8, so the byte count differs from the character count.

Converting#

Python
text = "café"

data = text.encode("utf-8")     # str -> bytes
print(data)                     # b'caf\xc3\xa9'

back = data.decode("utf-8")     # bytes -> str
print(back)                     # café

A mnemonic that sticks: you encode text into bytes for storing or sending, and decode bytes back into text for reading.

UTF-8 is the default for both, so these are equivalent:

Python
text.encode()
text.encode("utf-8")

Being explicit is still worth it. It documents the assumption, and the assumption is the thing that breaks.

UnicodeDecodeError#

Python
data = "café".encode("utf-8")
data.decode("ascii")
Output
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc3 in position 3:
ordinal not in range(128)

The message tells you three useful things: the encoding it tried, the byte that failed, and where. Position 3 is the accented character; byte 0xc3 is the first half of its UTF-8 representation, which ASCII has no meaning for.

Almost always the fix is to use the right encoding:

Python
data.decode("utf-8")      # correct

When you do not know the encoding#

Try the likely candidates in order:

Python
def try_decode(data, encodings=("utf-8", "utf-8-sig", "cp1252", "latin-1")):
    for encoding in encodings:
        try:
            return data.decode(encoding), encoding
        except UnicodeDecodeError:
            continue
    return data.decode("utf-8", errors="replace"), "utf-8 (with replacements)"


text, used = try_decode(raw_bytes)
print("decoded as", used)

Note that latin-1 never fails — every byte value is a valid character in it. That makes it useful as a last resort and dangerous as a default, because it turns a clear error into silently wrong text. Keep it last in the list.

For a real guess, use a detection library:

Terminal
python -m pip install charset-normalizer
Python
from charset_normalizer import from_bytes

result = from_bytes(raw_bytes).best()
print(result.encoding)
print(str(result)[:200])

The errors argument#

Python
data = b"caf\xe9"        # latin-1 bytes, decoded as utf-8

data.decode("utf-8", errors="strict")    # raises - the default
data.decode("utf-8", errors="replace")   # 'caf\ufffd' - a placeholder
data.decode("utf-8", errors="ignore")    # 'caf'  - the byte is dropped
data.decode("utf-8", errors="backslashreplace")  # 'caf\\xe9' - visible

Files#

Python
# Text mode - decoding happens for you
with open("data.txt", encoding="utf-8") as f:
    text = f.read()

# Binary mode - you get bytes and decode yourself
with open("data.txt", "rb") as f:
    data = f.read()
    text = data.decode("utf-8")

The byte order mark#

Python
data = b"\xef\xbb\xbfHello"

print(data.decode("utf-8"))       # '\ufeffHello' - invisible junk at the front
print(data.decode("utf-8-sig"))   # 'Hello' - BOM removed

Files saved by Excel and some Windows editors start with a three-byte marker. It is invisible when printed but breaks comparisons and CSV headers. Use utf-8-sig when reading files that may have one — it handles files with and without it.

Encodings worth knowing#

Encoding Covers Where you meet it
utf-8 every character the web, modern files, JSON
utf-8-sig as above, strips the BOM Excel exports
ascii 128 characters old protocols, identifiers
latin-1 256 characters older European files
cp1252 Windows Western legacy Windows exports
utf-16 every character some Windows APIs

Network and subprocess output#

Python
import requests

response = requests.get("https://example.com")
print(type(response.content))   # bytes - raw
print(type(response.text))      # str - decoded for you
Python
import subprocess

result = subprocess.run(["ls"], capture_output=True)
print(result.stdout.decode("utf-8", errors="replace"))

# Or let subprocess decode
result = subprocess.run(["ls"], capture_output=True, text=True, encoding="utf-8")
print(result.stdout)

Bytes behave like strings, mostly#

Python
data = b"hello world"

print(data.upper())          # b'HELLO WORLD'
print(data.split(b" "))      # [b'hello', b'world']
print(data[0])               # 104 - an int, not b'h'
print(data[0:1])             # b'h' - slicing gives bytes

b"hello" + "world"           # TypeError - cannot mix

Indexing a bytes object gives you an integer, while slicing gives you bytes. That asymmetry surprises people the first time.

Questions people ask#

What is the difference between Unicode and UTF-8?

Unicode assigns a number to every character. UTF-8 is one way of storing those numbers as bytes. A Python str holds Unicode characters; bytes holds an encoded form of them.

Why did Python 2 not have this problem?

It did — it just hid it, by allowing bytes and text to mix and converting implicitly. That produced errors far from their cause. Python 3 separates them deliberately, which is why the errors are noisier and much easier to fix.

How do I write a string to a file as bytes?

Open in binary mode and encode: open(path, "wb").write(text.encode("utf-8")). Or just use text mode with encoding="utf-8", which does it for you.

What does the b prefix mean?

It creates a bytes literal. b"abc" is three bytes; "abc" is three characters. Only ASCII characters can appear directly in a bytes literal.

Where to go next#

Python file handling, with encoding done properlyRead next

Keep reading

Keep going — pick your next guide

The fastest way to improve is to read one guide, then build the thing it describes. Start with the basics, or jump straight to a project.

Ask a question or share what worked

Your email address will not be published. Required fields are marked *