Python keyword: Reserved Words

Published on: August 7, 2026
Reading time: 5 minutes
Source code representing reserved words and soft keywords in Python

Code generators, form validators, refactoring tools, and systems that transform external names into Python identifiers need to know whether a word can be used as a variable, attribute, function, or class. The Python keyword module exposes the reserved words recognized by the current interpreter and the soft keywords that have special meaning only in selected grammatical contexts.

This guide covers iskeyword(), kwlist, issoftkeyword(), and softkwlist, including version-aware identifier validation. It complements our articles about tokenize, symtable, codeop, Python AST, and types.

What a reserved word is

Reserved words participate in Python’s grammar and cannot be ordinary identifiers.

class = "report"  # SyntaxError
return = 10       # SyntaxError

Familiar examples include class, def, return, if, for, while, try, import, and lambda. The list depends on the Python version and should not be hard-coded in long-lived tooling.

Check with iskeyword()

keyword.iskeyword() reports whether a string is a reserved word for the current interpreter.

import keyword

print(keyword.iskeyword("class"))    # True
print(keyword.iskeyword("customer")) # False

The function checks reserved-word status only. It does not validate every identifier rule.

Inspect kwlist

keyword.kwlist contains reserved words in alphabetical order.

for word in keyword.kwlist:
    print(word)

The list supports educational interfaces, syntax highlighting, tests, and alternative-name generation.

Do not mutate kwlist

Although the exposed object is a list, changing it does not alter Python’s grammar and can break code that relies on the module.

# Do not do this
keyword.kwlist.append("customer")

Treat it as read-only information. Copy it or build a frozenset for application use.

RESERVED = frozenset(keyword.kwlist)

What soft keywords are

Soft keywords receive special meaning only in certain grammatical contexts. Outside those contexts, they may remain valid identifiers.

This mechanism lets Python evolve while reducing breakage of existing programs. Modern features such as pattern matching and type declarations use contextual words.

Check soft keywords

print(keyword.issoftkeyword("match"))
print(keyword.issoftkeyword("case"))
print(keyword.issoftkeyword("customer"))

The official keyword documentation recommends querying the module rather than maintaining fixed lists because grammar evolves.

Inspect softkwlist

for word in keyword.softkwlist:
    print(word)

Not every soft keyword must be rejected as a name. The decision depends on where the identifier will appear.

A soft keyword may still be an identifier

match = "text"
case = 10
print(match, case)

In structural pattern matching, the same words participate in syntax.

match value:
    case 0:
        print("zero")

A code generator must consider the complete context instead of blocking every soft keyword unconditionally.

Validate a complete identifier

Combine str.isidentifier() with iskeyword().

def valid_identifier(name: str) -> bool:
    return name.isidentifier() and not keyword.iskeyword(name)

print(valid_identifier("customer_id"))
print(valid_identifier("2customers"))
print(valid_identifier("class"))

isidentifier() follows the Unicode identifier rules of the current release. It does not determine whether a name is readable or allowed by project policy.

Unicode names

Python accepts many Unicode characters in identifiers.

print("ação".isidentifier())
print("π".isidentifier())

The official lexical analysis reference describes normalization and permitted categories. Public APIs may prefer ASCII for interoperability, but that is a project policy rather than a language requirement.

Generate a safe identifier

import re
import unicodedata


def make_identifier(text: str) -> str:
    text = unicodedata.normalize("NFKC", text).strip()
    text = re.sub(r"\W+", "_", text, flags=re.UNICODE)
    text = text.strip("_") or "value"

    if text[0].isdigit():
        text = "_" + text

    if keyword.iskeyword(text):
        text += "_"

    if not text.isidentifier():
        raise ValueError("could not create identifier")

    return text

Do not use normalization alone for authorization decisions. Visually similar Unicode characters can still differ.

The trailing-underscore convention

class_ = "Report"
from_ = "source"
async_ = False

A trailing underscore is the conventional escape for reserved words. Keep a mapping between original external field names and Python identifiers.

Attribute names

Reserved words can be dictionary keys and can even be stored as dynamic attributes.

obj.__dict__["class"] = "special"
print(getattr(obj, "class"))

However, obj.class is invalid syntax. Code generators must reject or transform such names even when setattr() accepts them.

JSON and database fields

External schemas may legitimately contain class, from, or global. Do not silently alter the external schema.

FIELD_MAP = {
    "class": "class_",
    "from": "from_",
}

value = payload["class"]
model.class_ = value

Serialization back to the external format should restore the original name.

Generate function parameters

def safe_signature(names):
    for name in names:
        if not valid_identifier(name):
            raise ValueError(f"invalid parameter: {name!r}")
    return ", ".join(names)

Even with valid identifiers, concatenating and executing code remains dangerous when other parts come from users. Prefer closures, data structures, or restricted AST construction.

Keywords in templates

A template that generates classes, dataclasses, or APIs should validate class names, attributes, parameters, and methods separately.

A name accepted by getattr() is not necessarily valid after a dot in source code. Compile representative output in tests.

Compatibility across Python versions

A word may become reserved or contextual in a future release. Tools producing code for another runtime should not rely only on the interpreter running the generator.

Options include:

  • validating with the target interpreter;
  • maintaining versioned tables from official sources;
  • compiling generated code under every supported release;
  • using containers or a CI Python matrix.

Do not reuse one version’s list forever

Persisting an old kwlist indefinitely may permit syntax that is invalid in newer releases or reject words that changed status.

Record the interpreter version alongside generated artifacts and test minimum and maximum supported versions.

Syntax highlighting

Editors can use kwlist for hard keywords and softkwlist for contextual highlighting. Coloring every soft keyword everywhere creates false positives.

Accurate highlighting requires a tokenizer or parser. The keyword module provides classification, not grammatical position.

Integration with tokenize

tokenize commonly reports both identifiers and keywords as NAME tokens. The parser makes the final distinction.

import io
import tokenize

source = b"if value: pass\n"
for token in tokenize.tokenize(io.BytesIO(source).readline):
    if token.type == tokenize.NAME:
        print(token.string, keyword.iskeyword(token.string))

This supports lightweight analyzers, while soft keywords still need context.

Integration with AST

The parser applies the actual grammar of the current version. ast.parse() or compilation is the strongest confirmation that a complete source fragment is syntactically valid.

import ast

try:
    ast.parse("customer = 1")
except SyntaxError as error:
    print(error)

Parsing validates syntax without executing the source.

Refactoring tools

When renaming symbols, check:

  • whether the new name is an identifier;
  • whether it is a hard keyword;
  • whether it conflicts in the scope;
  • whether a soft keyword is valid in that context;
  • whether strings and configuration references also need updates.

symtable and AST help analyze scopes and references.

API validation

def validate_field(name):
    if not name.isidentifier():
        raise ValueError("field is not a Python identifier")
    if keyword.iskeyword(name):
        raise ValueError(f"{name!r} is reserved")

An API can offer an alternative automatically, but should clearly expose the mapping.

Performance

Module checks are fast. Tools validating millions of values can build immutable sets.

KEYWORDS = frozenset(keyword.kwlist)
SOFT_KEYWORDS = frozenset(keyword.softkwlist)

Rebuild these sets whenever the interpreter changes.

Testing

def test_identifiers():
    assert valid_identifier("customer")
    assert valid_identifier("ação")
    assert not valid_identifier("class")
    assert not valid_identifier("2customers")
    assert not valid_identifier("customer-id")

Include every current keyword, contextual words in several positions, Unicode, empty strings, and names close to built-ins.

Built-ins are not keywords

Names such as list, dict, str, id, and input are not reserved.

print(keyword.iskeyword("list"))  # False

Assigning to them is legal but shadows useful built-ins.

list = [1, 2, 3]
# list("abc") now fails

Linters can enforce this additional policy.

Common mistakes

  • Validating only with iskeyword().
  • Rejecting every soft keyword in every context.
  • Maintaining a stale manual list.
  • Assuming setattr() guarantees valid dot syntax.
  • Confusing built-ins with reserved words.
  • Generating for another version without testing it.
  • Mutating kwlist or softkwlist.
  • Executing code merely to validate syntax.

Best practices

  • Combine isidentifier() and iskeyword().
  • Evaluate soft keywords in context.
  • Query or test the actual target runtime.
  • Keep mappings for transformed external names.
  • Use AST or compilation for complete fragments.
  • Test all supported Python versions.
  • Lint shadowed built-ins.
  • Handle Unicode normalization deliberately.

Conclusion

The Python keyword module provides the official hard and contextual keyword lists for the running interpreter. iskeyword() and issoftkeyword() help validators, generators, editors, and refactoring tools follow language evolution.

Correct validation goes beyond one list: names must satisfy isidentifier(), soft keywords depend on context, and source intended for another version must be tested there. These layers let tools produce readable identifiers without generating invalid syntax.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Developer working with UTC timestamps and Python calendar.timegm
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    calendar.timegm: Convert UTC to Unix Timestamps

    Learn Python calendar.timegm to convert UTC date tuples into Unix timestamps safely and avoid local-time conversion bugs.

    Ler mais

    Tempo de leitura: 5 minutos
    14/09/2026
    Programmer analyzing code to identify file MIME types with Python
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    mimetypes.guess_file_type: Detect MIME Types

    Learn Python mimetypes.guess_file_type for MIME detection in paths, URLs, uploads, and HTTP responses with safe fallbacks.

    Ler mais

    Tempo de leitura: 5 minutos
    13/09/2026
    Server components representing isolated Python interpreters running in parallel
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: True Parallelism in Python

    Learn Python InterpreterPoolExecutor for CPU-bound tasks, isolated interpreters, true parallelism, and safer concurrency design.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Microprocessor representing CPUs available to a Python process
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    os.process_cpu_count: Count Available CPUs

    Learn Python os.process_cpu_count to size workers according to the CPUs actually available to the process.

    Ler mais

    Tempo de leitura: 4 minutos
    12/09/2026
    Laptop with digital code representing SQLite BLOB data
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    sqlite3.Blob: Incremental BLOB Reads and Writes

    Learn Python sqlite3.Blob for incremental BLOB reads and writes, lower memory use, and safer binary data handling in SQLite.

    Ler mais

    Tempo de leitura: 5 minutos
    12/09/2026
    Statistical analysis for Python random.binomialvariate
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    random.binomialvariate: Simulate Binomial Outcomes

    Learn Python random.binomialvariate to simulate successes, validate probabilities, and analyze binomial scenarios with practical examples.

    Ler mais

    Tempo de leitura: 5 minutos
    11/09/2026