Python’s keyword module provides the authoritative list of reserved keywords and tools to test strings against them. import keyword. Hard keywords: keyword.kwlist → list[str] — sorted list of all reserved keywords that cannot be used as identifiers (e.g. "if", "for", "def", "class", "import", "return", "yield", "lambda", "async", "await", "with", "try", "except", "finally", "raise", "del", "pass", "break", "continue", "global", "nonlocal", "and", "or", "not", "in", "is", "as", "from", "True", "False", "None"). Test: keyword.iskeyword(s) → bool. Soft keywords (Python 3.12+): keyword.softkwlist → list[str] — keywords that have special meaning only in specific syntactic positions ("match", "case", "type"); keyword.issoftkeyword(s) → bool. Soft keywords can still be used as variable names (e.g. match = re.match(...)). Combined: use both to detect all syntactically significant tokens. Claude Code generates keyword-aware identifier validators, safe variable name generators, reserved name checkers, code migration assistants, and Python version compatibility analyzers.
CLAUDE.md for keyword
## keyword Stack
- Stdlib: import keyword
- Hard: keyword.kwlist # all reserved keywords
- Test: keyword.iskeyword("for") # True
- keyword.iskeyword("match") # False (soft keyword)
- Soft: keyword.softkwlist # ["match","case","type"] (3.12+)
- keyword.issoftkeyword("match") # True
- Note: soft keywords are valid identifiers; hard keywords are not
keyword Keyword Analysis Pipeline
# app/keywordutil.py — validators, safe names, migration, compatibility
from __future__ import annotations
import builtins
import keyword
import re
import sys
from dataclasses import dataclass, field
# ─────────────────────────────────────────────────────────────────────────────
# 1. Keyword inspection helpers
# ─────────────────────────────────────────────────────────────────────────────
def all_keywords() -> list[str]:
"""
Return all hard keywords (reserved; cannot be identifiers).
Example:
print(all_keywords())
"""
return list(keyword.kwlist)
def all_soft_keywords() -> list[str]:
"""
Return all soft keywords (context-sensitive; valid as identifiers).
Returns empty list on Python < 3.9 where softkwlist is absent.
Example:
print(all_soft_keywords()) # ['match', 'case', 'type'] on 3.12+
"""
return list(getattr(keyword, "softkwlist", []))
def is_hard_keyword(name: str) -> bool:
"""Return True if name is a hard keyword."""
return keyword.iskeyword(name)
def is_soft_keyword(name: str) -> bool:
"""Return True if name is a soft keyword (context-sensitive)."""
check = getattr(keyword, "issoftkeyword", None)
return check(name) if check else False
def is_any_keyword(name: str) -> bool:
"""Return True if name is a hard or soft keyword."""
return is_hard_keyword(name) or is_soft_keyword(name)
def keyword_type(name: str) -> str:
"""
Return 'hard', 'soft', or 'none' for a given name.
Example:
print(keyword_type("if")) # "hard"
print(keyword_type("match")) # "soft"
print(keyword_type("foo")) # "none"
"""
if is_hard_keyword(name):
return "hard"
if is_soft_keyword(name):
return "soft"
return "none"
# ─────────────────────────────────────────────────────────────────────────────
# 2. Identifier validators
# ─────────────────────────────────────────────────────────────────────────────
def is_valid_identifier(name: str) -> bool:
"""
Return True if name is a valid Python identifier (str.isidentifier)
and is not a hard keyword.
Example:
print(is_valid_identifier("my_var")) # True
print(is_valid_identifier("for")) # False (keyword)
print(is_valid_identifier("123abc")) # False (not an identifier)
"""
return name.isidentifier() and not keyword.iskeyword(name)
def is_safe_attribute_name(name: str) -> bool:
"""
Return True if name is safe to use as an attribute/variable name:
valid identifier, not a hard keyword, and not a builtin name.
Example:
print(is_safe_attribute_name("data")) # True
print(is_safe_attribute_name("list")) # False (builtin)
print(is_safe_attribute_name("for")) # False (keyword)
"""
if not is_valid_identifier(name):
return False
return not hasattr(builtins, name)
@dataclass
class IdentifierCheck:
"""
Detailed check result for a proposed identifier name.
Example:
result = check_identifier("for")
print(result)
"""
name: str
valid: bool
issues: list[str] = field(default_factory=list)
suggestion: "str | None" = None
def __str__(self) -> str:
status = "OK" if self.valid else "INVALID"
issues = f" issues={self.issues}" if self.issues else ""
sug = f" suggestion={self.suggestion!r}" if self.suggestion else ""
return f"IdentifierCheck({self.name!r} {status}{issues}{sug})"
def check_identifier(name: str) -> IdentifierCheck:
"""
Check whether name is suitable as a Python identifier.
Flags hard keywords, soft keywords, builtins, and syntax errors.
Example:
print(check_identifier("match"))
print(check_identifier("class"))
print(check_identifier("my_variable"))
"""
issues: list[str] = []
suggestion: "str | None" = None
if not name:
return IdentifierCheck(name=name, valid=False, issues=["empty string"])
if not name.isidentifier():
issues.append("not a valid identifier syntax")
# Try stripping leading digits / replacing invalid chars
cleaned = re.sub(r"[^A-Za-z0-9_]", "_", name)
if cleaned and not cleaned[0].isdigit():
suggestion = cleaned
elif cleaned:
suggestion = "_" + cleaned
return IdentifierCheck(name=name, valid=False, issues=issues, suggestion=suggestion)
if keyword.iskeyword(name):
issues.append(f"reserved hard keyword")
suggestion = name + "_"
elif is_soft_keyword(name):
issues.append("soft keyword — valid but may confuse readers")
suggestion = name + "_"
elif hasattr(builtins, name):
issues.append(f"shadows built-in '{name}'")
suggestion = name + "_"
return IdentifierCheck(
name=name,
valid=(not issues or issues[0].startswith("soft keyword") or issues[0].startswith("shadows")),
issues=issues,
suggestion=suggestion,
)
# ─────────────────────────────────────────────────────────────────────────────
# 3. Safe name generator
# ─────────────────────────────────────────────────────────────────────────────
def safe_name(name: str) -> str:
"""
Return a safe identifier derived from name.
Hard keywords and non-identifier strings get a trailing underscore appended.
Soft keywords are left as-is (they're valid identifiers).
Example:
print(safe_name("class")) # "class_"
print(safe_name("for")) # "for_"
print(safe_name("match")) # "match" (soft keyword, ok)
print(safe_name("my-var")) # "my_var"
print(safe_name("123abc")) # "_123abc"
"""
# Replace hyphens and spaces
sanitized = re.sub(r"[^A-Za-z0-9_]", "_", name)
if sanitized and sanitized[0].isdigit():
sanitized = "_" + sanitized
if not sanitized:
sanitized = "_"
if keyword.iskeyword(sanitized):
sanitized = sanitized + "_"
return sanitized
def safe_names(names: list[str]) -> dict[str, str]:
"""
Apply safe_name to a list and return {original: safe} dict.
Example:
mapping = safe_names(["class", "for", "my-var", "ok"])
print(mapping)
"""
return {n: safe_name(n) for n in names}
# ─────────────────────────────────────────────────────────────────────────────
# 4. Keyword usage analysis
# ─────────────────────────────────────────────────────────────────────────────
def keywords_in_source(source: str) -> dict[str, int]:
"""
Count hard keyword occurrences in Python source using tokenize.
Returns {keyword: count} sorted by count descending.
Example:
counts = keywords_in_source(open("app.py").read())
print(counts)
"""
import io as _io
import tokenize as _tokenize
import token as _token
counts: dict[str, int] = {}
try:
tokens = _tokenize.generate_tokens(_io.StringIO(source).readline)
for tok in tokens:
if tok.type == _token.NAME and keyword.iskeyword(tok.string):
counts[tok.string] = counts.get(tok.string, 0) + 1
except _tokenize.TokenError:
pass
return dict(sorted(counts.items(), key=lambda x: -x[1]))
def unused_keywords(source: str) -> list[str]:
"""
Return keywords from kwlist that do NOT appear in source.
Useful for language-feature gap analysis.
Example:
missing = unused_keywords(open("app.py").read())
print("Features not used:", missing)
"""
used = set(keywords_in_source(source).keys())
return sorted(k for k in keyword.kwlist if k not in used)
# ─────────────────────────────────────────────────────────────────────────────
# 5. Python version compatibility
# ─────────────────────────────────────────────────────────────────────────────
# Keywords added per Python version
_NEW_IN_VERSION = {
(3, 5): {"async", "await"},
(3, 10): set(),
(3, 12): {"type"}, # soft keyword added as soft in 3.12
}
def keywords_available(major: int, minor: int) -> set[str]:
"""
Return the set of hard keywords available in Python major.minor.
Uses heuristics — only covers major additions.
Example:
compat = keywords_available(3, 8)
print("async available:", "async" in compat)
"""
# Python 3.0 base set
base = set(keyword.kwlist) - {"async", "await"}
if (major, minor) >= (3, 5):
base |= {"async", "await"}
return base
def uses_new_keywords(source: str, major: int, minor: int) -> list[str]:
"""
Return keywords used in source that are NOT available in major.minor.
Example:
new_kws = uses_new_keywords(source, 3, 4)
if new_kws:
print("Requires Python > 3.4:", new_kws)
"""
available = keywords_available(major, minor)
used = set(keywords_in_source(source).keys())
return sorted(used - available)
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== keyword demo ===")
# ── kwlist + softkwlist ────────────────────────────────────────────────────
print("\n--- all_keywords ---")
kws = all_keywords()
print(f" count: {len(kws)}")
print(f" first 10: {kws[:10]}")
print("\n--- all_soft_keywords ---")
soft = all_soft_keywords()
print(f" soft keywords: {soft}")
# ── iskeyword / issoftkeyword ─────────────────────────────────────────────
print("\n--- keyword_type ---")
for name in ["if", "for", "match", "case", "type", "foo", "list", "None"]:
print(f" {name!r}: {keyword_type(name)}")
# ── check_identifier ──────────────────────────────────────────────────────
print("\n--- check_identifier ---")
for name in ["my_var", "class", "match", "list", "123abc", "for", ""]:
result = check_identifier(name)
print(f" {result}")
# ── safe_names ────────────────────────────────────────────────────────────
print("\n--- safe_names ---")
mapping = safe_names(["class", "for", "match", "my-var", "123abc", "ok"])
for orig, safe in mapping.items():
print(f" {orig!r:12s} → {safe!r}")
# ── keywords_in_source ────────────────────────────────────────────────────
print("\n--- keywords_in_source ---")
sample = (
"async def fetch(url: str) -> None:\n"
" async with aiohttp.ClientSession() as session:\n"
" if url:\n"
" try:\n"
" return await session.get(url)\n"
" except Exception:\n"
" pass\n"
)
counts = keywords_in_source(sample)
for kw, cnt in list(counts.items())[:8]:
print(f" {kw:10s}: {cnt}")
# ── unused_keywords ───────────────────────────────────────────────────────
print("\n--- unused_keywords (sample) ---")
unused = unused_keywords(sample)
print(f" unused: {unused[:8]}")
# ── uses_new_keywords ─────────────────────────────────────────────────────
print("\n--- uses_new_keywords (python 3.4) ---")
new_kws = uses_new_keywords(sample, 3, 4)
print(f" keywords requiring > Python 3.4: {new_kws}")
print("\n=== done ===")
For the str.isidentifier() built-in alternative — "foo".isidentifier() checks whether a string would be syntactically valid as an identifier but does NOT filter out keywords — always combine str.isidentifier() with keyword.iskeyword() for a complete validity check; is_valid_identifier() in the pipeline above does exactly this. For the ast module alternative — ast.parse(source) reports keywords as ast.keyword nodes (used in function calls) and through the node types themselves, which is appropriate for structural AST analysis — use ast when you need to understand the tree; use keyword + tokenize when you need to count or locate keyword tokens at the lexical level. The Claude Skills 360 bundle includes keyword skill sets covering all_keywords()/all_soft_keywords()/is_hard_keyword()/is_soft_keyword()/keyword_type() inspection helpers, is_valid_identifier()/is_safe_attribute_name() validators, IdentifierCheck + check_identifier() detailed struct, safe_name()/safe_names() name sanitizers, keywords_in_source()/unused_keywords() source analysers, and keywords_available()/uses_new_keywords() version compat checkers. Start with the free tier to try keyword patterns and keyword pipeline code generation.