Skip to main content

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:

TypeWhat It HoldsExamples
intWhole numbers42, 0, -7, 1000
floatDecimal numbers3.14, 0.5, -2.7
strText (strings)"hello", "42", ""
boolTrue or FalseTrue, False

You already know these from Chapter 45, where you read them in code other people wrote. Now you write them yourself.

If you're new to programming

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.

Boolean naming convention

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 another language

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:

  1. Change the value to match the annotation
  2. 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 bool variable that starts with is_ or has_

Run uv run pyright on your file. Zero errors means you pass.


Try With AI

Opening Claude Code

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

  1. A data type tells Python what kind of value something is. 42 is an int, "42" is a str. They look similar but support completely different operations.

  2. Type annotations are promises. Writing age: int = 30 tells pyright (and future readers) that age will always hold an integer. Pyright enforces this promise at analysis time, not runtime.

  3. Variable names follow rules. Use snake_case, start with a letter or underscore, and never use reserved words like class or def.

  4. Pyright catches type mismatches before you run anything. Assigning a str to a variable annotated as int produces 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.