Types as Labels — str, int, float, bool
In Chapter 15, James installed the discipline stack and created SmartNotes with uv init. Now he opens the SmartNotes main.py and sees something new. The function definition reads def main() -> str:. James points at the arrow. "What does -> str mean?"
Emma does not rush to answer. She picks up a glass jar from the shelf, the kind with a screw-on lid. It has a label on the front: SUGAR. She holds it up. "Before you open this jar, what is inside?" James shrugs. "Sugar. The label says so." Emma nods. "That is exactly what -> str does. It is a label. It tells you this function returns text. You know what is inside before you run the code."
James looks at the screen again. -> str no longer looks cryptic. It is a label on a jar.
The Four Primitive Types
Python has four primitive types. Each one stores a different kind of value.
| Type | What It Stores | Example Value | Annotation |
|---|---|---|---|
str | Text | "Zia" | name: str = "Zia" |
int | Whole numbers | 25 | age: int = 25 |
float | Decimal numbers | 9.99 | price: float = 9.99 |
bool | True or False | True | active: bool = True |
Here is what those annotations look like together in code:
Loading Python environment...
Output:
(No output — these are declarations, not print statements.)
Each line follows the same pattern: name, colon, type, equals sign, value. The colon and type are the label. The value after the equals sign is what the jar holds.
Type Annotations as Labels
A type annotation is a label you attach to a variable. It tells anyone reading the code — human or tool — what kind of value belongs there.
There are three forms you will see:
Form 1: Annotation with value (most common)
Loading Python environment...
Output:
Read as: "greeting is labeled str, and holds the text Hello, SmartNotes!"
Form 2: Annotation without value (declares intent, assigns later)
Loading Python environment...
Output:
Read as: "result will hold an int. It gets assigned 42 on the next line."
Form 3: Function return annotation
Loading Python environment...
Output:
Read as: "main takes no arguments and returns a str."
Every annotation answers the same question: what type is inside this jar?
None — The Absence of Value
Sometimes a jar is empty. Python represents "nothing" with None.
Loading Python environment...
Output:
Read as: "middle_name holds either a str or None. Right now it holds None."
The str | None annotation means the variable can hold text or nothing. This pattern appears everywhere in real code because many values are optional. A user might not have a middle name. A search might find no results.
You check for None with is:
Loading Python environment...
Output:
No middle name provided
None is also falsy — it acts like False when used in a condition. You will see why that matters in the truthiness section below.
Type Conversions
Python provides four functions that convert values between types: int(), str(), float(), and bool().
Loading Python environment...
Output:
42
3.14
"100"
True
These work for clean conversions. But some conversions fail or surprise you.
| Conversion | Result | Why |
|---|---|---|
int("42") | 42 | Clean: digits only |
int("3.14") | ValueError | int() cannot parse a decimal string |
int(3.14) | 3 | Truncates toward zero (drops .14) |
float("3.14") | 3.14 | Clean: valid decimal string |
str(True) | "True" | Converts bool to its text name |
bool("0") | True | Non-empty string — even "0" is truthy |
bool("") | False | Empty string is falsy |
bool(0) | False | Zero is falsy |
bool(1) | True | Non-zero int is truthy |
The two traps to memorize:
int("3.14")raises an error. Useint(float("3.14"))if you need to convert a decimal string to an integer.bool("0")isTrue. The string"0"is not empty, so it is truthy. Only the empty string""is falsy.
Truthiness and Falsiness
Every Python value can act as True or False when used in a condition. A value that acts as False is called falsy. Everything else is truthy.
The complete falsy list:
| Falsy Value | Type | Why It Is Falsy |
|---|---|---|
False | bool | It is literally False |
0 | int | Zero |
0.0 | float | Zero |
"" | str | Empty string |
None | NoneType | Absence of value |
[] | list | Empty list |
{} | dict | Empty dict |
Everything else is truthy. This includes values that look false but are not:
Loading Python environment...
Output:
True
True
True
True
The rule is simple: empty or zero means falsy; anything else means truthy.
Before vs After: Typed vs Untyped
Here is the same function written without types, then with types.
Without types:
Loading Python environment...
What is data? A list of numbers? A list of strings? A tuple? You cannot tell from the code. You would have to read every line of the body, find where process is called, and trace the argument back to its source.
With types:
Loading Python environment...
Output:
Read as: "process takes a list of ints and returns an int."
The typed version answers three questions without reading the body:
- What goes in? A
list[int]. - What comes out? An
int. - What is the function's purpose? Summing a list of integers.
Types are documentation that the machine also reads. Pyright uses these annotations to catch bugs before you run the code. You will see exactly how in Lesson 5.
Exercises
Read and Predict 1: Type Identification
Look at each variable. What type does the annotation declare? What value does it hold?
Loading Python environment...
Predict first. Then check: str holding "Karachi", int holding 16000000, float holding 38.5, bool holding False. The underscore in 16_000_000 is a visual separator — Python ignores it.
Read and Predict 2: Truthiness
For each value, predict whether bool() returns True or False:
Loading Python environment...
Predict first. Answers: True, False, True, False, False, True, False, True. The last one is the trap — "False" is a non-empty string.
Read and Predict 3: Type Conversions
Predict the result of each conversion. If it raises an error, say so.
Loading Python environment...
Predict first. Answers: 100, ValueError, 7.0, "False", True (space is not empty), 1 (True converts to 1, False to 0).
Spot the Bug
This function says it returns an int, but something is wrong. Find the bug.
Loading Python environment...
The annotation promises -> int, but str(len(words)) returns a str. The fix: remove the str() call and return len(words) directly. Pyright would flag this as a return type mismatch.
Try With AI
Prompt 1: Generate Typed Variables
Generate 5 Python variables with different types.
For each one, use a type annotation and assign a value.
Then explain each annotation in plain English using
the format: "[name] is labeled [type] and holds [value]."
What you're learning: How to ask AI to produce typed examples and verify you can read every annotation. You already know the four types. Now you are checking whether AI's output matches your understanding. If AI generates an annotation you cannot read, that is your next learning target.
Prompt 2: Truthiness Quiz
Show me 10 Python values and ask me to predict whether
bool() returns True or False for each. Include at least
two values that are surprisingly truthy and two that are
surprisingly falsy. After I predict, show the answers.
What you're learning: How to use AI as a quiz generator for truthiness rules. The values you get wrong reveal gaps in your mental model. Truthiness trips up experienced developers too — practicing with varied examples builds reliable intuition.
Prompt 3: None in the Real World
Explain the difference between str | None and str with
a real-world analogy. Then show me a Python function that
returns str | None and explain when it returns None.
What you're learning: How to ask AI for conceptual explanations using analogies. str | None is a pattern you will see in every serious Python codebase. Understanding it now — before you encounter it in SmartNotes — means you will not be confused when it appears in Lesson 4 and beyond.
Key Takeaways
-
Python has four primitive types.
strstores text,intstores whole numbers,floatstores decimals,boolstoresTrueorFalse. Every variable in this book carries a type annotation. -
Type annotations are labels.
name: str = "Zia"reads as "name is labeled str and holds Zia." Labels tell you what is inside a variable without running the code. -
None means absence.
str | Nonemeans the variable holds text or nothing. Check for it withis None. -
Type conversions have traps.
int("3.14")raises an error.bool("0")isTrue. Memorize the falsy list:False,0,0.0,"",None,[],{}. -
Types make code readable. A function annotated
def process(data: list[int]) -> int:tells you what goes in and what comes out — no body reading required.
Looking Ahead
You can now read types and annotations. But real code does more than declare variables — it combines them. In Lesson 3, you will read expressions like price * quantity + tax and predict exactly what Python computes, in what order, and what type the result has. You will build your first trace table — tracking variable state line by line — which is the single most reliable technique for predicting output.