Programming with Python › Python Setup and First Programs › Day 44
Hands-on lab — Day 44: Variables and Types
- ← Back to the Day 44 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/programming-with-python/day-044-variables-and-types/
Commands
Setup
cd labs/sections/programming-with-python/day-044-variables-and-types Run
python3 examples/types_demo.py
python3 starter/types_demo.py Test
bash tests/run_tests.sh File tree
examples/types_demo.py expected-output/example-run.txt expected-output/FIELDS.md expected-output/test-run.txt metadata.yml README.md requirements/README.md security.md starter/types_demo.py starter/types-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 044 lab — Explore Python Types
Lesson
- Lesson title: Variables and Types
- Day number: 44 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-044-variables-and-types
- Lab files: everything you need is in this directory — follow “How to run” below.
- Browse the course locally: from the repository root, this lab also appears in the course website at
/labs/day-044-variables-and-typeswhen the site is running.
Purpose
Day 44's lesson explains what a Python variable really is — a name bound to an
object — and introduces the core built-in types. This lab makes it concrete:
you run a program that assigns a value of each core type and prints its
type(), demonstrates dynamic typing by re-binding a name, shows mutability
with id(), and converts types safely with try/except. Then you complete
a starter version yourself and fill in a short worksheet.
Learning objectives
- Run a Python program from the terminal with
python3and read its output. - Identify the core built-in types (
int,float,bool,str,NoneType,list) from a program's output. - Use
type()andid()to inspect an object's type and identity. - Show that a list mutates in place (its id is unchanged) while a string "change" makes a new object (its id changes).
- Convert a string to an integer safely, handling
ValueError.
Prerequisites
- The Day 44 lesson (read it first — it explains every concept this lab uses).
- Day 43 completed: Python 3 installed and runnable as
python3. - A terminal and any text editor. No third-party packages, account, or network connection required.
Supported operating systems
- macOS — fully supported (authored and tested on Apple Silicon, Python 3.14.0).
- Linux — fully supported (any distribution with Python 3.8+).
- Windows — supported; invoke the interpreter as
pythonifpython3is not found, or run the lab inside WSL for a Unix-style shell.
Hardware requirements
Any computer that can run Python 3. The programs allocate a handful of small objects and print text; they need no meaningful RAM, disk, or GPU.
Required software
python3(3.8 or newer; tested on 3.14.0).bash(to run the test script) — preinstalled on macOS and Linux.
Free and open-source options
Everything in this lab is free and open source: Python and bash ship with or install freely on every supported OS, and the lab uses only the standard language — no paid tools, no API keys, no accounts.
Installation
None beyond Python itself (from Day 43). Change into this directory and you are ready:
cd labs/sections/programming-with-python/day-044-variables-and-types
File structure
day-044-variables-and-types/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── types_demo.py ← YOUR working file (4 exercises)
│ └── types-worksheet.md ← worksheet for the practice assignment
├── examples/
│ └── types_demo.py ← completed reference program
├── tests/
│ └── run_tests.sh ← automated checks
├── expected-output/
│ ├── example-run.txt ← real captured run of the example
│ ├── test-run.txt ← real captured run of the tests
│ └── FIELDS.md ← what must appear on every platform
├── requirements/
│ └── README.md ← dependency statement (Python 3 only)
├── troubleshooting.md
└── security.md
How to run
From this directory:
## 1. See the finished result first
python3 examples/types_demo.py
## 2. Your task: complete the four exercises in the starter, then run it
python3 starter/types_demo.py
## 3. Check your work
bash tests/run_tests.sh
What the commands do
python3 examples/types_demo.py— runs the reference program: prints each core type withtype(), re-binds a name to show dynamic typing, comparesid()before and after a list append (same object) and a string concatenation (new object), and converts"30"and"oops"with a safeint()wrapped intry/except ValueError.python3 starter/types_demo.py— the same ideas as four numbered exercises for you to complete. EachNonemarked with an# exercisecomment is a blank to fill; edit the file in any text editor.bash tests/run_tests.sh— runs the example program and checks its output for every core type name, the dynamic-typing marker, both mutability results, and both conversion outcomes; then runs an independentpython3 -cassertion of the data model. Exits 0 on success, non-zero on any failure.
Expected output
See expected-output/example-run.txt — a
real captured run:
--- Core types ---
count = 42 type = int
price = 19.99 type = float
is_open = True type = bool
label = widget type = str
missing = None type = NoneType
--- Dynamic typing ---
thing = 100 (int)
thing = hello (str) <- same name, different type
--- Mutability ---
list before: [1, 2, 3] id unchanged after append: True
str before: cat id changed after '+': True
--- Safe conversion ---
'30' -> 30 (int)
'oops' -> could not convert (ValueError handled)
The id() comparisons (True) are the point: the list keeps its identity
after .append(), while the string gets a new identity after +. Your own
run prints the same lines (identity numbers are internal and not shown).
Validation steps
- Run
python3 examples/types_demo.py— it must exit without errors and print all four sections. - Complete the four exercises in
starter/types_demo.pyand run it — no line should still readtype = NoneTypefor the exercise-1 values, and the mutability line should reportTrue. - Confirm the safe conversion in exercise 2 prints
42as anint. - Run the tests (next section) — all checks must pass.
Tests
bash tests/run_tests.sh
Expected final line: 12 checks, 0 failure(s). The command exits 0 on
success and non-zero on any failure, so it can run in CI. The full captured
run is in expected-output/test-run.txt.
Cleanup
Nothing to clean up: the programs write no files and change no settings. To
reset your work on the starter, restore it from git:
git checkout -- starter/types_demo.py.
Troubleshooting
See troubleshooting.md for the full list (python3 not
found, Python 2 vs 3, SyntaxError from copied REPL prompts, ValueError on
conversion, indentation errors, differing id() numbers).
Security notes
See security.md. Short version: the programs make no network
calls, need no elevated privileges, read no input, and write no files — and
they deliberately use int()/float() with try/except, never eval(),
to convert text.
Extension exercises
- Add a
tupleand adictto the example's core-types section and print their types; confirmtupleis immutable anddictis mutable usingid(). - Extend the starter to prove aliasing: bind
b = afor a list, append throughb, and show thatasees the change. - Add a type hint to a variable (
n: int = "hello"), run the file to see Python ignore it, then read one page of the mypy documentation and note what a static checker would report.
Navigation
- Previous day: Day 43 — Installing Python and Virtual Environments
(
labs/sections/programming-with-python/day-043-installing-python-and-virtual-environments/). - Next day: Day 45 — Strings and Text Processing
(
labs/sections/programming-with-python/day-045-strings-and-text-processing/, to be written).
Expected output
FIELDS.md
# Expected output — Day 044 lab
This directory holds real captured runs from the authoring machine
(macOS, Apple Silicon, Python 3.14.0, 2026-07-12):
- `example-run.txt` — the full console output of `python3 examples/types_demo.py`.
- `test-run.txt` — the full console output of `bash tests/run_tests.sh`.
## What must appear on every platform
`python3 examples/types_demo.py` is deterministic **except** for the identity
numbers, which reflect memory addresses and differ every run. A correct run
always prints, regardless of OS or Python 3.x version:
1. The five core-type lines with type names `int`, `float`, `bool`, `str`,
and `NoneType`.
2. A dynamic-typing block where the name `thing` prints first as `int`, then
as `str` (`same name, different type`).
3. A mutability block asserting `id unchanged after append: True` (list) and
`id changed after '+': True` (string).
4. A safe-conversion block: `'30' -> 30 (int)` and
`could not convert (ValueError handled)` for `'oops'`.
`bash tests/run_tests.sh` ends with `12 checks, 0 failure(s).` and exits 0.
## Platform notes
- **macOS / Linux:** identical output; both use `python3`.
- **Windows:** the interpreter may be invoked as `python` rather than
`python3`; the printed lines are the same. Run inside PowerShell or WSL.
- The literal `id()` numbers are intentionally not captured as a stable
value — only the *comparisons* (`True`) are stable and checked.
example-run.txt
--- Core types ---
count = 42 type = int
price = 19.99 type = float
is_open = True type = bool
label = widget type = str
missing = None type = NoneType
--- Dynamic typing ---
thing = 100 (int)
thing = hello (str) <- same name, different type
--- Mutability ---
list before: [1, 2, 3] id unchanged after append: True
str before: cat id changed after '+': True
--- Safe conversion ---
'30' -> 30 (int)
'oops' -> could not convert (ValueError handled)
test-run.txt
Testing examples/types_demo.py ...
ok: example program exits successfully
ok: prints 'type = int'
ok: prints 'type = float'
ok: prints 'type = bool'
ok: prints 'type = str'
ok: prints 'type = NoneType'
ok: shows dynamic re-binding
ok: list mutates in place (id unchanged)
ok: string is immutable (new object on '+')
ok: safe conversion succeeds on '30'
ok: safe conversion handles bad input
Asserting the data model with python3 -c ...
ok: python3 -c data-model assertions pass
12 checks, 0 failure(s).
Source files
examples/types_demo.py (3137 bytes)
#!/usr/bin/env python3
"""Day 044 lab — a working tour of Python's core data model.
Run it with: python3 examples/types_demo.py
It demonstrates, in order:
1. A value of each core built-in type, printed with its type().
2. Dynamic typing: one name re-bound to a different type.
3. Mutability: a list mutated in place (id unchanged) versus a string
"modified" (id changes because a new object is made).
4. Safe type conversion with try/except so bad input is handled, not fatal.
Nothing here needs the network or any third-party package — only the
standard Python 3 interpreter you installed on Day 43.
"""
def show_core_types():
"""Assign one value of each core type and print each with its type()."""
print("--- Core types ---")
count = 42 # int — a whole number
price = 19.99 # float — a number with a fractional part
is_open = True # bool — a truth value
label = "widget" # str — text
missing = None # NoneType — the deliberate "no value" object
# type(x).__name__ gives the short type name, e.g. 'int' instead of
# "<class 'int'>", which reads more cleanly in a report.
for name, value in [
("count", count),
("price", price),
("is_open", is_open),
("label", label),
("missing", missing),
]:
print(f"{name:<10} = {str(value):<13} type = {type(value).__name__}")
def show_dynamic_typing():
"""Re-bind one name to a value of a different type — legal in Python."""
print("\n--- Dynamic typing ---")
thing = 100 # thing points at an int
print(f"thing = {thing} ({type(thing).__name__})")
thing = "hello" # now it points at a str
print(f"thing = {thing} ({type(thing).__name__}) <- same name, different type")
def show_mutability():
"""Show a list mutating in place (same id) vs a string making a new object."""
print("\n--- Mutability ---")
numbers = [1, 2, 3]
id_before = id(numbers)
numbers.append(4) # mutate the SAME list object in place
print(f"list before: [1, 2, 3] id unchanged after append: {id(numbers) == id_before}")
text = "cat"
id_before = id(text)
text = text + "s" # builds a NEW string; text is re-bound
print(f"str before: cat id changed after '+': {id(text) != id_before}")
def safe_int(raw):
"""Convert a string to an int, returning None instead of crashing on bad input."""
try:
return int(raw)
except ValueError:
return None
def show_safe_conversion():
"""Demonstrate safe type conversion with try/except ValueError."""
print("\n--- Safe conversion ---")
for raw in ["30", "oops"]:
converted = safe_int(raw)
if converted is None:
print(f"{raw!r} -> could not convert (ValueError handled)")
else:
print(f"{raw!r} -> {converted} ({type(converted).__name__})")
def main():
show_core_types()
show_dynamic_typing()
show_mutability()
show_safe_conversion()
if __name__ == "__main__":
main()
metadata.yml (575 bytes)
lesson_id: D044
day: 44
kind: python-program
languages: [python]
setup_commands:
- cd labs/sections/programming-with-python/day-044-variables-and-types
run_commands:
- python3 examples/types_demo.py
- python3 starter/types_demo.py
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/types_demo.py # optional: reset your work'
requires_network: false
requires_api_key: false
estimated_minutes: 30
last_executed: '2026-07-12'
executed_on: 'macOS (Apple Silicon), Python 3.14.0, bash tests/run_tests.sh → 12 checks, 0 failure(s).'
requirements/README.md (779 bytes)
# Dependencies — Day 044 lab
**Python 3 only.** This lab has zero third-party dependencies:
- `python3` (version 3.8 or newer; authored and tested on 3.14.0). Installed
in the Day 43 lesson. Check yours with `python3 --version`.
- `bash` (3.2 or newer — preinstalled on macOS and every mainstream Linux
distribution) to run the test script.
Everything the programs use — `type()`, `id()`, `int()`, `str()`, `float()`,
`isinstance()`, lists, and try/except — is part of the Python standard
language. There is deliberately no `requirements.txt` or `pip install` step;
the programs must run on a fresh Python installation with nothing added.
On Windows, invoke the interpreter as `python` if `python3` is not found, or
run the lab inside WSL for a Unix-style shell.
starter/types_demo.py (3154 bytes)
#!/usr/bin/env python3
"""Day 044 lab — YOUR working file. Complete the four numbered exercises.
Run it with: python3 starter/types_demo.py
Each exercise names the exact function to use. Replace every `None` marked
with an "# exercise" comment with real code, then run the file. The finished
reference version is examples/types_demo.py — try to complete this yourself
first, then compare.
"""
def exercise_1_three_types():
"""Exercise 1: create variables of THREE different types and print each
with its type name, using type(value).__name__.
Pick three different types (for example an int, a str, and a bool).
Replace the three `None` values below with values of your choice, then
the loop will print each with its type.
"""
print("--- Exercise 1: three types ---")
value_a = None # exercise 1a: assign an int, e.g. 7
value_b = None # exercise 1b: assign a str, e.g. "hello"
value_c = None # exercise 1c: assign a bool, e.g. True
for name, value in [("value_a", value_a), ("value_b", value_b), ("value_c", value_c)]:
print(f"{name} = {value!r} type = {type(value).__name__}")
def exercise_2_safe_conversion():
"""Exercise 2: convert the string "42" to an int SAFELY.
Use int(...) inside a try/except ValueError. Set `result` to the integer
on success, or the string "conversion failed" if a ValueError is raised.
Fill in the two exercise lines.
"""
print("\n--- Exercise 2: safe conversion ---")
raw = "42"
result = None
try:
result = None # exercise 2a: use int(raw) here
except ValueError:
result = None # exercise 2b: set this to "conversion failed"
print(f"{raw!r} converted to: {result!r} type = {type(result).__name__}")
def exercise_3_list_is_mutable():
"""Exercise 3: show that a list is MUTABLE.
Start from [1, 2, 3], record id() before, append the number 4, then print
whether the id stayed the same (it should — a list mutates in place).
Fill in the two exercise lines.
"""
print("\n--- Exercise 3: a list is mutable ---")
numbers = [1, 2, 3]
id_before = id(numbers)
# exercise 3a: append the number 4 to `numbers` using numbers.append(...)
same_object = None # exercise 3b: set to (id(numbers) == id_before)
print(f"numbers is now {numbers} same object after append: {same_object}")
def exercise_4_none_vs_zero():
"""Exercise 4: distinguish absent (None) from present-but-zero (0).
Write a check that prints "absent" when value is None and "present"
otherwise — even when the value is 0. Use `is not None` (NOT a plain
truthiness test, which would wrongly call 0 absent). Fill in the exercise line.
"""
print("\n--- Exercise 4: None vs zero ---")
for value in [0, None, 5]:
present = None # exercise 4: set to (value is not None)
label = "present" if present else "absent"
print(f"value = {value!r} -> {label}")
def main():
exercise_1_three_types()
exercise_2_safe_conversion()
exercise_3_list_is_mutable()
exercise_4_none_vs_zero()
if __name__ == "__main__":
main()
starter/types-worksheet.md (1699 bytes)
# Types worksheet — Day 044
Fill this in using the Python REPL (start it with `python3`). Verify every
answer live rather than from memory, and paste the REPL lines that prove each
claim. Keep this worksheet — the next lesson on strings builds on it.
## 1. The type of three values
Choose three values of three *different* types. For each, run `type(value)`
in the REPL and record the result.
| Value you tested | `type(value)` reports | Type name |
| ---------------- | --------------------- | --------- |
| _e.g._ `42` | | |
| | | |
| | | |
Paste your REPL lines here:
```text
(paste, e.g.)
>>> type(42)
<class 'int'>
```
## 2. Is a string mutable?
Answer in one sentence: **is a `str` mutable or immutable?**
> Your answer:
Now prove it with `id()`. Record `id(s)` before and after "changing" a
string, and say whether the id stayed the same or changed, and what that
tells you.
```text
(paste your REPL session, e.g.)
>>> s = "cat"
>>> id(s)
...
>>> s = s + "s"
>>> id(s)
...
```
> What the ids show:
## 3. A conversion that fails
Find one type conversion that raises an error. Record exactly what you
converted, the exact error message, and *why* it fails (in terms of this
lesson).
- **What you converted:** _e.g._ `int("3.5")`
- **Exact error:**
- **Why it fails:**
```text
(paste your REPL line and the traceback)
```
## Check
- [ ] Every row of section 1 is filled with a real REPL result.
- [ ] Section 2 states mutable/immutable correctly and cites id() evidence.
- [ ] Section 3 shows a real conversion error with an explanation.
tests/run_tests.sh (3279 bytes)
#!/usr/bin/env bash
# Tests for the Day 044 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies that the example program prints the expected type names and the
# correct mutability results, and independently asserts Python's data-model
# behaviour with `python3 -c`. No network access is used.
set -u
lab_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
failures=0
checks=0
check() {
local label="$1" ok="$2"
checks=$((checks + 1))
if [ "${ok}" = "yes" ]; then
echo " ok: ${label}"
else
echo " FAIL: ${label}"
failures=$((failures + 1))
fi
}
# Require a working Python 3 interpreter.
if ! command -v python3 >/dev/null 2>&1; then
echo "FAIL: python3 not found on PATH — install Python 3 (see Day 43)."
exit 1
fi
echo "Testing examples/types_demo.py ..."
if ! output="$(python3 "${lab_dir}/examples/types_demo.py" 2>&1)"; then
check "example program exits successfully" "no"
echo "${output}" | sed 's/^/ /'
echo
echo "${checks} checks, ${failures} failure(s)."
exit 1
fi
check "example program exits successfully" "yes"
# Each core type name must appear in the output.
for type_name in "type = int" "type = float" "type = bool" "type = str" "type = NoneType"; do
echo "${output}" | grep -q "${type_name}" \
&& check "prints '${type_name}'" "yes" \
|| check "prints '${type_name}'" "no"
done
# Dynamic typing: the same name shows two different types.
echo "${output}" | grep -q "same name, different type" \
&& check "shows dynamic re-binding" "yes" \
|| check "shows dynamic re-binding" "no"
# Mutability results: list id unchanged after append; str id changed after '+'.
echo "${output}" | grep -q "id unchanged after append: True" \
&& check "list mutates in place (id unchanged)" "yes" \
|| check "list mutates in place (id unchanged)" "no"
echo "${output}" | grep -q "id changed after '+': True" \
&& check "string is immutable (new object on '+')" "yes" \
|| check "string is immutable (new object on '+')" "no"
# Safe conversion: good input converts, bad input is handled, not fatal.
echo "${output}" | grep -q "'30' -> 30 (int)" \
&& check "safe conversion succeeds on '30'" "yes" \
|| check "safe conversion succeeds on '30'" "no"
echo "${output}" | grep -q "could not convert (ValueError handled)" \
&& check "safe conversion handles bad input" "yes" \
|| check "safe conversion handles bad input" "no"
# Independent assertion of the data model, straight from the interpreter.
echo "Asserting the data model with python3 -c ..."
if python3 -c '
a = [1, 2, 3]
b = a
b.append(4)
assert a == [1, 2, 3, 4], "aliasing: a should see b'"'"'s append"
assert isinstance(True, int), "bool is a subtype of int"
s = "cat"; before = id(s); s = s + "s"
assert id(s) != before, "str must be a new object after +"
n = [1]; before = id(n); n.append(2)
assert id(n) == before, "list must mutate in place"
try:
int("3.5"); raise SystemExit("int(\"3.5\") should have raised ValueError")
except ValueError:
pass
assert bool("False") is True, "non-empty string is truthy"
'; then
check "python3 -c data-model assertions pass" "yes"
else
check "python3 -c data-model assertions pass" "no"
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 044 lab
python3: command not found
Python 3 is not installed or not on your PATH. Revisit the Day 43 lesson
("Installing Python and Virtual Environments"). On some systems — notably
Windows — the command is python, not python3. Check which one reports a
3.x version:
python3 --version # try this first
python --version # fall back to this on Windows
Use whichever prints Python 3.x. This lab was authored on Python 3.14.0,
but any Python 3.8+ produces the same results.
python runs Python 2 (prints Python 2.x)
On older machines the bare python command may be Python 2, whose print
and integer division differ. Always use python3 for this course. If only
Python 2 is available, install Python 3 (see Day 43).
SyntaxError after copying lines from the lesson
The lesson shows REPL sessions with >>> prompts. Those prompts are not
part of the code. In a .py file, write only the code — no >>> and no
expected-output lines.
ValueError: invalid literal for int() with base 10
This is int() correctly refusing a string that is not a whole number (for
example int("3.5") or int("oops")). It is expected behaviour, not a bug.
In the exercises, make sure such conversions sit inside the try block so
the except ValueError handles them.
IndentationError or TabError
Python uses indentation to define blocks, and mixing tabs and spaces breaks it. Indent with spaces (four per level is the convention) and keep it consistent. Most editors can "convert tabs to spaces" in their settings.
The id() numbers in my run differ from the captured output
That is correct and expected — identities reflect memory addresses and change
every run and every machine. Only the comparisons (True/False) are
stable; those are what the tests check.
bash: tests/run_tests.sh: No such file or directory
Run the command from the lab directory itself, not from a parent folder:
cd labs/sections/programming-with-python/day-044-variables-and-types
bash tests/run_tests.sh
Security notes
Security notes — Day 044 lab
-
What the programs do: they create a few values, print their types and identities, and demonstrate a safe conversion. They make no network connections, write no files, read no input from you, and change no settings. They run entirely as your normal user; nothing here needs
sudo. -
Never use
eval()(orexec()) to convert untrusted input. It is tempting to turn a string into a value witheval("42"), butevalruns any Python code the string contains — a string like__import__("os").system("...")would execute a real command. To turn text into a number, use the type constructors this lesson teaches —int(),float()— which parse a value and nothing else, and wrap them intry/except ValueErrorto reject bad input safely. The lab does exactly this and never callseval. -
Treat all external input as untrusted text. Keyboard input, file contents, and network responses arrive as strings of unknown shape. Convert them to the exact type you expect, inside a
try/except, and reject anything that does not fit, rather than assuming a value is already the type you want. Type confusion at these boundaries is a common root cause of real security bugs. -
Reading before running: both programs are short and commented — read them before running. Running unread code is one of the most common ways developers get compromised; every script in this course is small enough to read and understand first.