Every Python program is built out of names: variables, functions, classes, modules. Some names are user-defined. A small fixed set is reserved by the language for its own grammar, and those can't be used as user-defined names. This lesson covers the full list of reserved keywords (with what each one is for), the rules for what makes a valid identifier, the naming styles the community follows, and the underscore conventions that signal intent.
A keyword is a word reserved by the Python parser as part of the language's grammar. if, def, class, and return are keywords. When the parser sees one of them, it expects specific syntax to follow. if can't be redefined, and it can't be used as a variable name, even if the rest of the line would have been valid.
Assigning to a keyword raises a SyntaxError at parse time:
Python expects class to be followed by a class name and a colon, the start of a class definition. The = confuses it, and it gives up before the file runs. The fix is to rename the variable to something that isn't a keyword:
Keywords are off-limits as names. The rest of this section walks through which words are reserved, grouped by what they're for.
As of Python 3.13 there are 35 keywords. The exact list grows or shifts slightly between versions, but the core set hasn't changed in many years. Grouping them by purpose makes them easier to learn than reading the alphabetical list.
| Category | Keywords | What they do |
|---|---|---|
| Literals | True, False, None | The three constant values built into the language |
| Conditionals | if, elif, else | Branching based on a condition |
| Loops | for, while, break, continue | Iteration and loop control |
| Definitions | def, class, lambda | Define functions, classes, and anonymous functions |
| Return | return, yield | Send a value out of a function or generator |
| Logical | and, or, not | Combine and invert boolean expressions |
| Membership and identity | in, is | Test membership and object identity |
| Imports | import, from, as | Bring code from other modules into the current namespace |
| Exceptions | try, except, finally, raise, assert | Catch and raise errors |
| Context managers | with | Set up and tear down a runtime context |
| Scope | global, nonlocal | Tell Python where to find or store a name |
| Async | async, await | Define and await coroutines |
| Other | pass, del | A do-nothing statement and a name-removal statement |
The grouped view helps with identifying which words to avoid as variable names and what role each word plays in a Python program.
A few are worth a sentence of context:
True, False, and None are the only built-in singletons Python has. There's only one of each in a running program, no matter how many times the word appears.pass is a statement that does nothing. It's a placeholder when a block body is otherwise empty.lambda is the keyword for anonymous functions: square = lambda x: x * x defines a one-line function with no name of its own.del removes a name binding (or an item from a collection).with is for resource management, like opening a file and ensuring it closes.assert is a debugging aid that raises AssertionError when a condition is false.The keyword list also includes a couple of words that aren't always keywords, covered in the soft keywords section below.
keyword ModulePython ships with a small standard-library module called keyword that lists which words are reserved in the running version. It's useful for tools that generate code, for editor plugins, and for runtime checks.
keyword.kwlist is a regular Python list of strings, so it can be iterated, counted, or passed around.
For a one-off check, keyword.iskeyword(name) returns True or False:
"class" is reserved, "category" isn't, and "True" is the literal value. The function takes a string, not the value itself. Writing keyword.iskeyword(class) would itself be a SyntaxError.
keyword.softkwlist is the list of soft keywords, covered in the section below:
These are reserved only in specific contexts. Outside those contexts, they can be used as ordinary names.
keyword.kwlist is a regular list, so iteration is fine. For checking many names in a tight loop, building a set(keyword.kwlist) first and checking name in that_set is O(1) per check instead of O(n).
An identifier is the name assigned to any Python object: variable, function, class, module, parameter. The language has a small set of hard rules about what characters an identifier can contain. Breaking these rules causes the file to fail to parse.
The rules:
a-z, A-Z) or an underscore (_).price, Price, and PRICE are three different names.Valid identifiers:
All six parse. Python doesn't care about the style. customerName mixes capitalization, cart2 ends in a digit, and _internal_counter starts with an underscore, but every name follows the character rules.
Invalid identifiers each raise a different error.
Starts with a digit:
Python reads the 2, assumes a number is being written, then gets confused by nd_product. The fix is to rename the variable so it starts with a letter or underscore: second_product.
Contains a hyphen:
The hyphen is the subtraction operator. Python parses unit-price as unit minus price, which isn't a name you can assign to. Use an underscore instead: unit_price.
Contains a space:
Spaces split a line into separate tokens. customer and name are two different tokens to the parser, and it doesn't know what to do with two names where it expected one.
Uses a reserved keyword:
This is the same SyntaxError from the start of the lesson. Reserved keywords are off-limits, regardless of how valid the surrounding character rules look.
That's the entire check the parser runs. The rules are mechanical and small, which keeps accidental invalid names rare.
Python 3 allows non-ASCII letters in identifiers. The exact set is defined by Unicode's identifier classification, which covers most letter characters in most scripts.
All three names parse and work. Most codebases stick to ASCII because mixed scripts can render inconsistently across editors and systems, and a name that uses a Cyrillic а instead of a Latin a can pass a visual check while being a different identifier underneath. The feature exists because Python is used worldwide and forcing every developer to transliterate words into ASCII isn't fair, but the common convention is ASCII names.
Digits and punctuation that aren't part of the Unicode letter category still aren't allowed. So cart2 is fine (2 is a digit), but emoji and most symbols aren't (they aren't classified as letters).
The character rules above are enforced by the parser. PEP 8 naming conventions aren't. Python doesn't care if a class is named customer or a constant taxRate. The community does, and the standard library follows these styles. Code that follows the conventions reads consistently with the rest of the ecosystem.
The core table:
| What you're naming | Convention | Example |
|---|---|---|
| Variable | snake_case | cart_total, unit_price |
| Function | snake_case | apply_discount(), is_in_stock() |
| Module (file) | snake_case | cart_utils.py, order_helpers.py |
| Class | PascalCase | Product, ShoppingCart |
| Constant | UPPER_SNAKE_CASE | TAX_RATE, FREE_SHIPPING_THRESHOLD |
A short example showing each style in the same file:
A reader who knows the conventions can tell at a glance what each name is, even without reading the definitions: TAX_RATE is a fixed value, ShoppingCart is a class, apply_tax is a function, cart and final_total are local variables. The visual contrast is the purpose.
_var Convention: "Internal Use"A single leading underscore on a name is a convention that says "this is meant for internal use, don't rely on it from outside the module or class." Python doesn't enforce anything here. It's a signal, not a hard rule.
final_price is the public entry point. _add_tax and _DEFAULT_DISCOUNT are implementation details. Nothing in Python stops a caller from invoking _add_tax(100) directly, but the leading underscore communicates "this isn't a stable interface, it can change without warning."
The one place Python does treat a leading underscore specially is the from module import * form. Names starting with _ are skipped by that import, so a wildcard import won't pull in internal helpers. That's the one place the convention has teeth.
A trailing underscore (class_, type_) is the convention for working around a name clash with a keyword or built-in. def filter_products(class_="Electronics"): uses class_ because class is reserved.
Names that start and end with two underscores are called dunder names (short for "double underscore"). They aren't keywords, but Python reserves them for its own use, and inventing custom dunder names should be avoided.
The most common ones:
__init__ is the constructor Python calls when an instance is created. __repr__ defines how the object should be displayed. __class__ is the attribute Python sets automatically to point at the object's class. There are dozens more (__len__, __add__, __iter__, and so on), called dunder methods or, more formally, special methods.
Two reasons not to invent custom dunder names:
__my_thing__ is confusing in a code review.For names that mean "internal but related to a specific class", the convention is a single leading underscore. The _DEFAULT_DISCOUNT example earlier is a good template.
There's also a single-trailing-underscore form for instance attributes that would otherwise clash with Python's own names: cls_ = MyClass works when a parameter or attribute called cls is needed.
The full table:
| Pattern | Meaning | Example |
|---|---|---|
name | Public, normal use | cart_total |
_name | Internal use, soft convention | _DEFAULT_DISCOUNT |
name_ | Avoid clash with a keyword or built-in | class_, type_ |
__name | Strongly private inside a class, name mangling kicks in | __secret_key |
__name__ | Reserved by Python for special methods and attributes | __init__, __repr__ |
The double-leading-underscore pattern (__name) is rare and triggers name mangling in classes: the attribute becomes _ClassName__name internally. It's used when a subclass might accidentally clash with a parent's private attribute.
match, case, type, _Python 3.10 introduced pattern matching with the match statement. The words match, case, and the underscore _ (as a wildcard pattern) are treated as keywords only inside match blocks. Outside those blocks, they can be used as ordinary names. They're called soft keywords for this reason.
Soft keywords are context-sensitive. Inside the match statement below, match and case are keywords. Outside, they're ordinary identifiers:
Both pieces of code parse and run. The parser determines from context whether match is the start of a pattern-matching statement or a variable name. The _ inside case _: is the wildcard pattern, but outside a match block, _ is the conventional "ignore this value" variable name, as in for _ in range(5):.
type joined the soft keyword list more recently (Python 3.12) because of the new type alias statement: type Vector = list[float]. Outside that specific statement, type remains the built-in function.
Soft keywords don't matter for most day-to-day code. Outside pattern matching or type aliases, they behave like normal names. match, case, type, and _ switch into keyword behavior only in those specific syntactic positions, which is why editors highlight them inside a match block but not outside one.
There's no runtime cost to soft keywords. The parser handles the context-sensitivity at compile time.
A short tour of identifier-related mistakes that come up with new Python developers.
What's wrong with this code?
These names are all valid identifiers, and the parser accepts them. The problem is that they shadow built-in names. Inside the current scope, print is now a string, not the print function. The call print("Hello") raises TypeError: 'str' object is not callable. The fix is to pick names that don't collide with built-ins. For a name like type, use type_ with a trailing underscore.
What's wrong with this code?
Python is case-sensitive. Customer_Name (with capitals) is a different name from customer_name. The code raises NameError: name 'Customer_Name' is not defined. Pick a casing convention and use it everywhere. PEP 8 says snake_case for variables.
What's wrong with this code?
This is technically legal, but inadvisable. Dunder names are reserved for Python's own use. Python won't actively stop you, but a future version might give __my_special_method__ a special meaning, and any reader will look at this name and wonder which special protocol it implements. Use a single leading underscore instead for "internal use", or a normal name if it doesn't need to be marked as internal.
What's wrong with this code?
class here isn't a string, it's an attempt to use the keyword itself, which is a SyntaxError. keyword.iskeyword takes a string. Pass "class" with quotes: keyword.iskeyword("class").
10 quizzes