pandas-dev/pandas · error · ValueError

is a python keyword

Error message

{k} is a python keyword

What it means

Raised by `register_option` when a path component is a valid identifier syntactically but is a Python keyword (`if`, `for`, `class`, `lambda`, ...). Keywords are rejected via `keyword.iskeyword(k)` because they cannot be used as attribute names on `DictWrapper`.

Solutions

  1. Rename the keyword component to a non-keyword (e.g. `for_`, `class_`, or a descriptive name).
  2. Check each segment with `import keyword; keyword.iskeyword(seg)` before registering.

Example fix

# before
register_option('class.enabled', True)   # ValueError: python keyword

# after
register_option('class_.enabled', True)
Defensive patterns

Strategy: validation

Validate before calling

import keyword
def no_keyword_segment(key: str) -> bool:
    return not any(keyword.iskeyword(p) for p in key.split('.'))
if no_keyword_segment(new_key):
    register_option(new_key, default)

Type guard

def is_keyword_free_path(key: str) -> bool:
    import keyword
    return not any(keyword.iskeyword(p) for p in key.split('.'))

Try / catch

try:
    register_option(key, default)
except ValueError as e:
    if 'python keyword' in str(e):
        # rename keyword segments (e.g. append '_') and retry
        ...
    raise

Prevention

When it happens

Trigger: `register_option('for.value', ...)`, `register_option('class.enabled', ...)`, or any component that is a Python keyword.

Common situations: Naming an option after a language concept that happens to be reserved; auto-generating keys from external vocabulary that collides with keywords.

Related errors


AI-assisted analysis of pandas-dev/pandas@3b7651241d (2026-08-11). Data as JSON: /api/errors/562932498be5706f. Report an issue: GitHub.

Appendix: source

Thrown at pandas/_config/config.py:576

    key = key.lower()

    if key in _registered_options:
        raise OptionError(f"Option '{key}' has already been registered")
    if key in _reserved_keys:
        raise OptionError(f"Option '{key}' is a reserved key")

    # the default value should be legal
    if validator:
        validator(defval)

    # walk the nested dict, creating dicts as needed along the path
    path = key.split(".")

    for k in path:
        if not re.match("^" + tokenize.Name + "$", k):
            raise ValueError(f"{k} is not a valid identifier")
        if keyword.iskeyword(k):
            raise ValueError(f"{k} is a python keyword")

    cursor = _global_config
    msg = "Path prefix to option '{option}' is already an option"

    for i, p in enumerate(path[:-1]):
        if not isinstance(cursor, dict):
            raise OptionError(msg.format(option=".".join(path[:i])))
        if p not in cursor:
            cursor[p] = {}
        cursor = cursor[p]

    if not isinstance(cursor, dict):
        raise OptionError(msg.format(option=".".join(path[:-1])))

    # a namespace already lives here, i.e. `key` is a path prefix to one or
    # more already-registered options; registering it would clobber them
    # (GH#29242)
    if isinstance(cursor.get(path[-1]), dict):

View on GitHub (pinned to 3b7651241d)