python/cpython · error · ValueError

binary mode doesn't take an encoding argument

Error message

binary mode doesn't take an encoding argument

What it means

Raised by io.open() as a ValueError when binary mode ('b') is combined with a non-None encoding argument. Binary streams carry raw bytes with no codec, so an encoding is meaningless and rejected to prevent silent misuse.

Source

Thrown at Lib/_pyio.py:223

        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")
    if binary and encoding is not None:
        raise ValueError("binary mode doesn't take an encoding argument")
    if binary and errors is not None:
        raise ValueError("binary mode doesn't take an errors argument")
    if binary and newline is not None:
        raise ValueError("binary mode doesn't take a newline argument")
    if binary and buffering == 1:
        import warnings
        warnings.warn("line buffering (buffering=1) isn't supported in binary "
                      "mode, the default buffer size will be used",
                      RuntimeWarning, 2)
    raw = FileIO(file,
                 (creating and "x" or "") +
                 (reading and "r" or "") +
                 (writing and "w" or "") +
                 (appending and "a" or "") +
                 (updating and "+" or ""),
                 closefd, opener=opener)
    result = raw
    try:

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. In binary mode, drop the encoding argument (pass None or omit it).
  2. In wrappers, forward encoding only for text mode: kwargs = {'encoding': enc} if 'b' not in mode else {}.
  3. If you actually want decoding, use text mode ('r') instead of 'rb'.

Example fix

// before
def load(path, encoding='utf-8'):
    with open(path, 'rb', encoding=encoding) as f:  # ValueError
        return f.read()

// after
def load(path, encoding='utf-8', binary=False):
    mode = 'rb' if binary else 'r'
    kwargs = {} if binary else {'encoding': encoding}
    with open(path, mode, **kwargs) as f:
        return f.read()
Defensive patterns

Strategy: validation

Validate before calling

def open_any(path, mode='r', encoding=None, errors=None, newline=None):
    if 'b' in mode:
        encoding = errors = newline = None      # strip text-only kwargs
    return open(path, mode, encoding=encoding, errors=errors, newline=newline)

Type guard

def kwargs_ok_for_mode(mode: str, **kwargs) -> bool:
    return 'b' not in mode or all(v is None for v in (kwargs.get('encoding'),))

Prevention

When it happens

Trigger: open('f.bin', 'rb', encoding='utf-8'). Very common when encoding is passed unconditionally from a wrapper function (def read(path, encoding='utf-8'): open(path, 'rb', encoding=encoding)) while the mode is caller-controlled.

Common situations: Utility wrappers that always forward an encoding keyword; switching a file from text to binary mode while leaving the encoding argument in place; data-processing defaults like pd-adjacent code that threads encoding through every open call.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/599452b569c244f6. Report an issue: GitHub.