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.

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.

Appendix: 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)