Keywords & Identifiers
Keywords are the words Python reserves for its own syntax; identifiers are the names you choose for everything else — this topic covers the exact rules for both, including Unicode names, soft keywords, and shadowing built-ins.
Every name in a Python program is either a keyword — one of a small, fixed set of words with built-in syntactic meaning — or an identifier you've chosen yourself for a variable, function, class, or other object. Getting the rules right here (what counts as a valid identifier, which words are off-limits, and which words merely look reserved but aren't) avoids an entire class of confusing errors.
Reserved Keywords
Keywords are words reserved by the Python grammar itself; you cannot use them as variable, function, or class names under any circumstances — doing so is a SyntaxError. As of Python 3.12 there are 35 hard keywords.
False await else import pass
None break except in raise
True class finally is return
and continue for lambda try
as def from nonlocal while
assert del global not with
async elif if or yieldimport keyword
print(keyword.kwlist) # the full list of hard keywords, programmatically
print(keyword.iskeyword("if")) # True
print(keyword.iskeyword("data")) # FalseSoft (Contextual) Keywords
Python 3.10+ introduced soft keywords: words that have special syntactic meaning only in specific contexts, but remain completely valid as ordinary identifiers everywhere else. This lets Python add new syntax without breaking existing code that happens to use those words as variable names.
type also became a soft keyword in Python 3.12, used in the new type Alias = int syntax for declaring type aliases; _ is a soft keyword specifically as the wildcard pattern inside match/case blocks.
match = "not the match statement here" # perfectly legal — match is a soft keyword
print(match)
# But used at the start of a statement, followed by a pattern-matching structure,
# match is interpreted as the match statement's keyword:
command = "start"
match command:
case "start":
print("starting")
case _:
print("unknown command")import keyword
print(keyword.softkwlist) # ['_', 'case', 'match', 'type']Identifier Rules
An identifier must start with a letter or underscore, followed by any number of letters, digits, or underscores — and it must not be a hard keyword. Identifiers are case-sensitive.
valid_name = 1
_hidden = 2
CamelCase = 3
name2 = 4
__dunder__ = 5
2fast = 6 # SyntaxError — cannot start with a digit
my-name = 7 # SyntaxError — hyphen is not allowed (looks like subtraction)
class = 8 # SyntaxError — 'class' is a reserved keywordUnicode Identifiers
Python 3 identifiers are not limited to ASCII — letters from any Unicode script are permitted, following the formal rules in PEP 3131 (broadly: Unicode letters and the underscore can start an identifier; letters, digits, and underscore can continue it). Digits (Unicode or ASCII) still cannot appear as the first character.
Just because Unicode identifiers are legal doesn't mean they're advisable for shared codebases — ASCII identifiers remain overwhelmingly conventional in professional Python code for portability, tooling compatibility, and to avoid visually confusable characters.
área = 12.5 # valid — accented Latin letters allowed
名前 = "Ada" # valid — Unicode script identifiers allowed
π = 3.14159 # valid — Greek letters allowed
print(área, 名前, π)Naming Conventions
PEP 8 establishes strong conventions for how different kinds of names are cased, even though Python itself enforces almost none of them syntactically:
These are style conventions the community adheres to strongly, not rules the interpreter checks — MyVariable = 5 runs perfectly fine; it's simply non-idiomatic.
snake_case - variables, functions, methods, module names
UPPER_SNAKE_CASE - constants (see the Constants & Naming Conventions topic)
CapWords/PascalCase - class names
_leading_underscore - convention for "internal use" (not enforced privacy)
__leading_double - triggers name mangling inside classes (see Classes topic)
__dunder__ - reserved pattern for special/"magic" methods and attributesInvalid Names — Common Errors
A handful of mistakes cause identifier-related SyntaxErrors repeatedly for newcomers: starting with a digit, using a hyphen instead of an underscore, embedding spaces, using a reserved keyword as a name, or using punctuation that isn't underscore.
1st_place = "gold" # SyntaxError — starts with a digit
first-place = "gold" # SyntaxError — hyphen isn't valid in identifiers
first place = "gold" # SyntaxError — space breaks it into two tokens
def = "keyword" # SyntaxError — 'def' is reserved
first@place = "gold" # SyntaxError — '@' is not a valid identifier characterShadowing Built-ins
Built-in names like list, str, dict, len, id, type, and input are not keywords — they're ordinary names that live in the builtins namespace. This means you can legally reassign them (list = [1, 2, 3] runs without error), but doing so shadows the built-in for the rest of that scope, silently breaking any later code that expected list to still mean the built-in type or function. This is a real, common bug — not a syntax error, so it's often discovered only when something confusingly fails much later.
Common built-in names accidentally shadowed this way: list, dict, str, id, type, input, sum, max, min, and format. A linter (like Ruff or Pylint, both third-party tools) will typically flag this as a warning.
list = [1, 2, 3] # shadows the built-in list type in this scope
print(list) # [1, 2, 3] — looks fine so far...
numbers = list(range(5)) # TypeError: 'list' object is not callable
# because "list" now refers to your variable, not the builtin typeComplete Example
import keyword
candidates = ["total_price", "class", "match", "2fast", "área", "list"]
for name in candidates:
is_kw = keyword.iskeyword(name)
is_soft = keyword.issoftkeyword(name)
print(f"{name!r}: hard keyword={is_kw}, soft keyword={is_soft}")'total_price': hard keyword=False, soft keyword=False
'class': hard keyword=True, soft keyword=False
'match': hard keyword=False, soft keyword=True
'2fast': hard keyword=False, soft keyword=False # (and would be a SyntaxError if used as a real name)
'área': hard keyword=False, soft keyword=False
'list': hard keyword=False, soft keyword=False # a shadowable built-in, not a keywordKey Takeaways
Hard keywords (like if, def, class, and, is) can never be used as identifiers — doing so is a SyntaxError.
Soft keywords (match, case, _, type) are only special in specific syntactic contexts and remain valid identifiers everywhere else.
A valid identifier starts with a Unicode letter or underscore, continues with letters/digits/underscores, and is case-sensitive.
Built-in names like list and str are ordinary names, not keywords — reassigning them is legal but shadows the built-in and can cause confusing later errors.
PEP 8's naming conventions (snake_case, PascalCase, UPPER_SNAKE_CASE) are style guidance, not interpreter-enforced rules.
list = [1, 2, 3] #... much later... result = list(range(10)) # TypeError: 'list' object is not callable numbers = [1, 2, 3] #... much later... result = list(range(10)) # list still refers to the builtin type
Choosing a variable name that matches a built-in type or function shadows it in that scope, which can silently break unrelated code later that still expects the built-in behavior.
class = "Algebra" # SyntaxError: 'class' is a reserved keyword course_name = "Algebra"
Hard keywords like class can never be used as identifiers, regardless of context — pick a descriptive name that isn't reserved.
match = 5 match match: case 5: print("five") # confusing to read even though technically legal value = 5 match value: case 5: print("five")
Using match as a variable name is technically legal (it's a soft keyword) but reads confusingly right next to the match statement — avoid reusing soft-keyword spellings as ordinary names even where the language permits it.
What is the difference between a hard keyword and a soft keyword in Python?
A hard keyword (like if, def, class, or, is) can never be used as an identifier anywhere — it's always reserved. A soft keyword (like match, case, _, or type) only has special syntactic meaning in specific contexts and remains a perfectly valid identifier everywhere else.
Is café = 10 valid Python? Explain why or why not.
Yes, it's valid. Python 3 identifiers can include Unicode letters, not just ASCII, following PEP 3131 — café starts with a letter and continues with letters, satisfying the identifier rules.
Why doesn't list = [1, 2, 3] raise any error, even though list is the name of a built-in type?
Because list is not a keyword — it's an ordinary name defined in the builtins namespace. Ordinary names can be reassigned freely; doing so shadows the built-in within that scope rather than triggering a syntax error, which is exactly why it's a common and dangerous mistake.
Give three examples of invalid Python identifiers and state which rule each one breaks.
1st_place breaks the rule against starting with a digit. my-name breaks the rule against non-underscore punctuation (the hyphen looks like subtraction). def breaks the rule against using a reserved hard keyword as a name.
How would you programmatically check, in code, whether a given string is currently a Python keyword?
Use the standard-library keyword module: keyword.iskeyword("if") returns True for hard keywords; keyword.issoftkeyword("match") returns True specifically for soft keywords. keyword.kwlist and keyword.softkwlist give the full lists.
What naming convention does PEP 8 recommend for a constant like a maximum retry count, and is that convention enforced by the interpreter?
PEP 8 recommends UPPER_SNAKE_CASE, e.g. MAX_RETRIES = 5. This is purely a stylistic convention — the interpreter does not enforce casing on any name, and max_retries = 5 would run identically; it's just non-idiomatic.
What does this print?
for True False match False True total False False case False True _ False True "for" is a hard keyword. "match", "case", and "_" are soft keywords (special only in context). "total" is an ordinary identifier, not reserved at all.
Write code that assigns 1, 2, 3 to id, then explain in a comment why calling id(x) afterward would fail.
id = 1 # shadows the built-in id() function in this scope # id(x) would now raise: TypeError: 'int' object is not callable, # because the name "id" no longer refers to the builtin function — # it refers to the int object 1 that was just assigned to it.
Because id is not a keyword, reassigning it is legal but shadows the built-in id() function for the rest of that scope; any later attempt to call id(x) fails since the name id now refers to an int, not a callable.
What happens when this line is run?
It raises a SyntaxError. def is a hard, reserved keyword in Python and can never be used as an identifier, regardless of context.
Write a valid identifier that uses a Unicode (non-ASCII) letter, and assign it the value 100.
This is valid Python 3 syntax — número starts with a Unicode letter and continues with Unicode letters, satisfying PEP 3131's identifier rules, even though ASCII names remain the overwhelming convention in shared codebases.
import keyword
for word in ["for", "match", "total", "case", "_"]:
print(word, keyword.iskeyword(word), keyword.issoftkeyword(word))