From Reading to Writing Types
James opens SmartNotes and types word_count = 350. He moves on to the next line.
Emma stops him. "What type is that?"
James looks at the screen. "It's a number. Obviously."
"Obviously to you. Not to pyright." She adds four characters after the variable name: : int. The line becomes word_count: int = 350. "Now pyright knows. Claude Code knows. And six months from now, you will know too."
James frowns. "But Python is dynamically typed. It figures out the type on its own. Why should I bother?"
Emma points at the screen. "Try calling word_count.upper() and see what happens."
James types it. Pyright immediately underlines the line in red: Cannot access attribute "upper" on type "int". He did not even run the code. The error appeared the moment he typed it.
"That is why," Emma says. "Python figures out types at runtime. Pyright figures them out before you run anything. The annotation : int is your promise, and pyright holds you to it."
What Is a Data Type?
A data type tells Python what kind of value something is. The number 42 and the text "42" look similar on screen, but Python treats them completely differently. You can divide 42 by 2 and get 21.0. Try dividing "42" by 2 and Python raises a TypeError, because dividing text makes no sense.
Python has four primitive types that cover most basic values:
| Type | What It Holds | Examples |
|---|---|---|
int | Whole numbers | 42, 0, -7, 1000 |
float | Decimal numbers | 3.14, 0.5, -2.7 |
str | Text (strings) | "hello", "42", "" |
bool | True or False | True, False |
You already know these from Chapter 45, where you read them in code other people wrote. Now you write them yourself.
A data type tells Python what kind of value something is. The number 42 is an int (integer). The text "42" is a str (string). They look similar but Python treats them differently: you can do math with numbers but not with text. Type annotations (age: int = 30) are your way of telling Python and its tools what type you expect each variable to be.
Inspecting Types with type()
Python has a built-in function called type() that tells you the type of any value. Open a Python session or use Claude Code to run these:
type(42) # <class 'int'>
type("42") # <class 'str'>
type(3.14) # <class 'float'>
type(True) # <class 'bool'>
Output:
<class 'int'>
<class 'str'>
<class 'float'>
<class 'bool'>
Notice: 42 is an int but "42" (with quotes) is a str. The quotes make the difference. This distinction matters every time you write a type annotation.
Comments: Notes for Humans
Before writing annotations, one quick tool. Comments start with #. Python ignores everything after # on that line:
# This is a comment. Python skips it entirely.
word_count: int = 350 # This comment explains the variable
Output:
No output. Comments produce nothing; they exist for the humans reading the code. Use comments to explain why something exists, not what it does. The code itself shows the "what."
Writing Your First Type Annotations
A type annotation sits between the variable name and the = sign. The pattern is always the same:
name: type = value
Here are four variables, one for each primitive type:
name: str = "James"
age: int = 30
price: float = 9.99
is_member: bool = True
Output (from print statements):
James
30
9.99
True
Each annotation makes a promise: name will always hold a str, age will always hold an int, and so on. Pyright enforces these promises.
Variable Naming Rules
Python has rules about what counts as a valid variable name. The convention in Python is snake_case: lowercase letters with underscores separating words.
# Good names (snake_case, descriptive)
word_count: int = 350
reading_time: float = 1.4
is_draft: bool = True
user_name: str = "Alice"
Some names are invalid. Python rejects them before your code even runs:
# Invalid names (these cause errors):
# 2fast = 10 # Cannot start with a number
# my-name = "Jo" # Hyphens not allowed (use underscores)
# class = "Math" # 'class' is a reserved word
Reserved words are names Python uses for its own syntax: class, def, return, True, False, None, and, or, not, if, for, while, and others. You cannot use these as variable names.
For bool variables, start the name with is_, has_, or can_ to make the True/False meaning obvious:
is_draft: bool = True(reads as "is draft? yes")has_password: bool = False(reads as "has password? no")can_edit: bool = True(reads as "can edit? yes")
Reassignment and Type Errors
Once you annotate a variable, you can reassign it to a new value of the same type:
age: int = 30
age = 31 # Fine: 31 is still an int
Output (from pyright):
0 errors, 0 warnings, 0 informations
But if you try to assign a value of a different type, pyright catches it:
age: int = 30
age = "thirty-one" # Wrong type!
Output (from pyright):
error: Type "str" is not assignable to type "int"
This is the promise in action. You said age is an int. Assigning a str breaks that promise. Pyright does not let it pass quietly. You see the error before the code ever runs.
If you know types from Java, TypeScript, or C#, Python's annotations work similarly but are NOT enforced at runtime. They are metadata for static analysis tools like pyright. Python remains dynamically typed underneath. If you removed the annotation and ran the code, Python would accept age = "thirty-one" without complaint. Pyright is the guardrail, not Python itself.
Putting It Together
Here is a complete set of SmartNotes variables with types, comments, and one deliberate error:
# SmartNotes: variables for a single note
title: str = "My First Note"
word_count: int = 350
reading_time: float = 1.4
is_draft: bool = True
# Update the word count after editing
word_count = 420 # Same type (int), no error
# Oops: wrong type
word_count = "four hundred" # pyright: Type "str" is not assignable to type "int"
Output (from pyright):
error: Type "str" is not assignable to type "int" (line 10)
Pyright catches the error on the last line. The fix: either keep word_count as an int (use word_count = 400) or change the annotation to str (but then you lose the ability to do math with it). Types force you to decide what a variable is for.
PRIMM-AI+ Practice: Spotting Type Errors
Predict [AI-FREE]
Look at these three assignments. Without running anything, predict which ones pyright will flag as errors. Write your predictions and a confidence score from 1 to 5 before checking.
temperature: float = 98.6
temperature = 100
user_name: str = "Bob"
user_name = 42
is_active: bool = True
is_active = False
Check your predictions
Assignment 1: temperature = 100. Pyright does NOT flag this. Why? Because int is compatible with float in Python's type system. An integer can be used where a float is expected. This is a special rule: int is a subtype of float.
Assignment 2: user_name = 42. Pyright DOES flag this. int is not assignable to str. You promised text; you gave a number.
Assignment 3: is_active = False. Pyright does NOT flag this. False is a bool, same type as True. No mismatch.
If you predicted 1 out of 3 correctly, the int-to-float rule is the tricky one. If you predicted 2-3 correctly, your type intuition is solid.
Run
Create a file called type_practice.py with the assignments above. Run uv run pyright type_practice.py. Compare the output to your predictions.
Investigate
For each flagged assignment, write a one-sentence explanation of why pyright flagged it. Use this format: "Pyright flagged this because [variable] was annotated as [type], but [value] is a [different type]."
For the temperature = 100 case that was NOT flagged, write a sentence explaining why int is allowed where float is expected.
Modify
Fix each flagged assignment so pyright passes. There are two strategies:
- Change the value to match the annotation
- Change the annotation to match the value
Try both strategies for user_name = 42. Which fix makes more sense for a variable called user_name?
Make [Mastery Gate]
Without looking at any examples, write 5 typed variable declarations from scratch. Requirements:
- Use all four types (
int,float,str,bool) at least once - Use valid snake_case names
- Include at least one
boolvariable that starts withis_orhas_
Run uv run pyright on your file. Zero errors means you pass.
Try With AI
If Claude Code is not already running, open your terminal, navigate to your SmartNotes project folder, and type claude. If you need a refresher, Chapter 44 covers the setup.
Prompt 1: Check Your Understanding
I just learned about Python type annotations. Here is my
understanding: "Type annotations tell Python what type a
variable is, and Python enforces them at runtime."
Is my summary accurate? What am I getting wrong?
Read AI's response carefully. Did it correct the runtime enforcement claim? (Python does NOT enforce annotations at runtime; pyright does.) Compare its correction to what you learned in this lesson.
What you're learning: You are testing your own understanding by stating it confidently and letting AI find the gap. This is the same verify-then-refine pattern you will use when reviewing AI-generated code.
Prompt 2: Generate and Review Typed Variables
Create 6 typed Python variables for a simple book tracker:
title, author, page count, current page, price, and whether
the book is finished. Use snake_case names and type annotations.
Include a comment explaining each variable.
Read AI's output. Check: did it use the correct types? Did it follow snake_case? Does the bool variable start with is_ or has_? If anything looks wrong, tell AI what to fix.
What you're learning: You are reviewing AI-generated annotations against the rules from this lesson. This is Step 5 (Read) from the TDG loop: you verify AI's work, you do not blindly accept it.
Key Takeaways
-
A data type tells Python what kind of value something is.
42is anint,"42"is astr. They look similar but support completely different operations. -
Type annotations are promises. Writing
age: int = 30tells pyright (and future readers) thatagewill always hold an integer. Pyright enforces this promise at analysis time, not runtime. -
Variable names follow rules. Use snake_case, start with a letter or underscore, and never use reserved words like
classordef. -
Pyright catches type mismatches before you run anything. Assigning a
strto a variable annotated asintproduces an immediate error. Fix it by changing the value or the annotation.
Looking Ahead
You can now write typed variables. In Lesson 2, you combine them: what happens when you multiply an int by a float? What does 10 / 3 return, and why is it not 3? Arithmetic with types has a few surprises that pyright helps you catch.