python/cpython · error · TypeError
invalid encoding: %r
Error message
invalid encoding: %r
What it means
Raised by io.open() when the encoding argument is supplied and is neither None nor a str. encoding is only meaningful in text mode, and the validator checks its type up front. Passing None (the default) is fine; any other non-string (bytes, list, int) raises this TypeError.
Source
Thrown at Lib/_pyio.py:203
mode, it returns a BufferedReader; in write binary and append binary
modes, it returns a BufferedWriter, and in read/write mode, it returns
a BufferedRandom.
It is also possible to use a string or bytearray as a file for both
reading and writing. For strings StringIO can be used like a file
opened in a text mode, and for bytes a BytesIO can be used like a file
opened in a binary mode.
"""
if not isinstance(file, int):
file = os.fspath(file)
if not isinstance(file, (str, bytes, int)):
raise TypeError("invalid file: %r" % file)
if not isinstance(mode, str):
raise TypeError("invalid mode: %r" % mode)
if not isinstance(buffering, int):
raise TypeError("invalid buffering: %r" % buffering)
if encoding is not None and not isinstance(encoding, str):
raise TypeError("invalid encoding: %r" % encoding)
if errors is not None and not isinstance(errors, str):
raise TypeError("invalid errors: %r" % errors)
modes = set(mode)
if modes - set("axrwb+t") or len(mode) > len(modes):
raise ValueError("invalid mode: %r" % mode)
creating = "x" in modes
reading = "r" in modes
writing = "w" in modes
appending = "a" in modes
updating = "+" in modes
text = "t" in modes
binary = "b" in modes
if text and binary:
raise ValueError("can't have text and binary mode at once")
if creating + reading + writing + appending > 1:
raise ValueError("can't have read/write/append mode at once")
if not (creating or reading or writing or appending):
raise ValueError("must have exactly one of read/write/append mode")View on GitHub (pinned to bc6749cc3b)
Solutions
- Pass a codec name string ('utf-8', 'latin-1', ...) or None.
- If the value may come from bytes config, decode it first: encoding=cfg_bytes.decode().
- For CodecInfo objects, pass codec.name instead of the object.
- Fix conditional expressions: encoding='utf-8' if use_utf8 else None.
Example fix
// before
enc = os.environb.get(b'ENC') # bytes or None
open('f', encoding=enc) # TypeError when bytes
// after
enc = os.environb.get(b'ENC', b'utf-8').decode('ascii')
open('f', encoding=enc) Defensive patterns
Strategy: type-guard
Validate before calling
if encoding is not None and not isinstance(encoding, str):
encoding = str(encoding) # e.g. decode bytes or take codec.name
open(path, encoding=encoding) Type guard
def valid_encoding(enc) -> bool:
return enc is None or isinstance(enc, str) Try / catch
try:
open(path, encoding=enc)
except TypeError as e:
if 'invalid encoding' in str(e) and isinstance(enc, (bytes, bytearray)):
open(path, encoding=bytes(enc).decode('ascii'))
else:
raise Prevention
- Decode bytes-sourced encoding names at the config boundary.
- Pass codec.name for codecs.lookup results.
- Write conditional encoding args as 'x' if cond else None, never cond and 'x'.
When it happens
Trigger: open('f', 'r', encoding=b'utf-8'), encoding=['utf-8'], or encoding pulled from a config/env layer that returns a non-string (e.g. a codecs.CodecInfo object, or None-vs-missing sentinel mishandling such as encoding=flag or None chains producing False).
Common situations: Encoding taken from environment or config that yields bytes (common in Py2-to-Py3 ports); passing the result of codecs.lookup() (a CodecInfo) instead of its .name; boolean expressions like encoding=use_utf8 and 'utf-8' evaluating to False.
Related errors
- invalid errors: %r
- invalid file: %r
- invalid mode: %r
- invalid buffering: %r
- can't have text and binary mode at once
AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14).
Data as JSON: /api/errors/a332ff7fa202653e.
Report an issue: GitHub.