Programming with Python › Python Setup and First Programs › Day 47
Hands-on lab — Day 47: Input, Output, and f-strings
- ← Back to the Day 47 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-047-input-output-and-f-strings/
Commands
Setup
cd labs/sections/programming-with-python/day-047-input-output-and-f-strings Run
python3 examples/io_demo.py 5 7
echo "5 7" | python3 examples/io_demo.py
python3 starter/io_demo.py 8 3 Test
bash tests/run_tests.sh File tree
examples/io_demo.py expected-output/FIELDS.md expected-output/sample-run.txt metadata.yml README.md requirements/README.md security.md starter/io_demo.py starter/io-worksheet.md tests/run_tests.sh troubleshooting.md
Lab README
Day 047 lab — A Friendly CLI Greeter/Calculator
Lesson
- Lesson title: Input, Output, and f-strings
- Day number: 47 of 365
- Lesson article: https://ai-roadmap-365.github.io/day-047-input-output-and-f-strings
- 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-047-input-output-and-f-stringswhen the site is running.
Purpose
Day 47's lesson explains how a program talks to people and to pipes. This lab
makes it concrete: you build io_demo.py, a small command-line tool that reads
two numbers either from a command-line argument or from standard input,
converts them safely, and prints a neatly f-string-formatted, aligned report —
sending errors to standard error and exiting non-zero on bad input. It is a
miniature of every well-behaved Unix command-line tool.
Learning objectives
- Read input from
sys.argvand fromsys.stdin, and choose which fits. - Convert text input to numbers safely with
float(), and see whyinput()returning a string matters. - Format numbers into aligned, fixed-decimal columns with f-string format specs.
- Route results to standard output and errors to standard error, and exit with a non-zero code on failure.
- Test a non-interactive program by driving it with pipes and arguments.
Prerequisites
- The Day 47 lesson (read it first — it explains every concept this lab uses).
- Days 43–46: a working Python 3, variables, strings, and numbers.
- Day 10: the shell and pipes.
- A terminal: Terminal.app (macOS), any terminal (Linux), or PowerShell/WSL (Windows).
Supported operating systems
- macOS — fully supported (tested on macOS with Python 3.14).
- Linux — fully supported (any distribution with Python 3 and bash).
- Windows — run the Python program directly in PowerShell
(
python examples\io_demo.py 5 7), or run everything unmodified inside WSL.
Hardware requirements
Any computer that runs Python 3. The lab does only trivial arithmetic and text formatting; it needs no particular RAM, disk, or GPU.
Required software
python33.6 or newer (f-strings and their format specs). Check withpython3 --version.bashto 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 its standard library, bash, and the standard shell utilities the tests use. No account, API key, or purchase is needed, and nothing runs over the network.
Installation
None beyond Python itself. Clone the repository (or copy this directory) and change into it:
cd labs/sections/programming-with-python/day-047-input-output-and-f-strings
If python3 --version prints a version, you are ready. If not, see
requirements/README.md.
File structure
day-047-input-output-and-f-strings/
├── README.md ← you are here
├── metadata.yml ← machine-readable lab metadata
├── starter/
│ ├── io_demo.py ← YOUR working file (5 numbered exercises)
│ └── io-worksheet.md ← worksheet for the practice assignment
├── examples/
│ └── io_demo.py ← completed reference implementation
├── tests/
│ └── run_tests.sh ← automated, non-interactive checks
├── expected-output/
│ ├── sample-run.txt ← real captured run (macOS, Python 3.14)
│ └── FIELDS.md ← required output fields 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 — both input paths
python3 examples/io_demo.py 5 7
echo "5 7" | python3 examples/io_demo.py
## 2. Your task: complete the five exercises in the starter, then run it
python3 starter/io_demo.py 8 3
echo "8 3" | python3 starter/io_demo.py
## 3. Check your work
bash tests/run_tests.sh
What the commands do
python3 examples/io_demo.py 5 7— runs the reference in argument mode: it joins the command-line arguments, splits them into two numbers, converts withfloat(), and prints the aligned report.echo "5 7" | python3 examples/io_demo.py— runs the same program in stdin mode: with no arguments it reads the piped text fromsys.stdin. The output is byte-for-byte identical to argument mode.python3 starter/io_demo.py 8 3— runs your in-progress version; until you finish the exercises it still containsFILL_ME_INplaceholders.bash tests/run_tests.sh— runs concept checks (python3 -c), then drives the reference program with pipes and arguments to verify aligned output, and confirms bad input goes to stderr with a non-zero exit. It checks your starter strictly once everyFILL_ME_INis gone, and structurally before that.
Expected output
See expected-output/sample-run.txt — a real
captured run. Good input prints an aligned report to standard output:
Input values
a 5.00
b 7.00
Results
sum 12.00
product 35.00
mean 6.00
Bad input prints nothing to stdout, writes an error: line to standard error,
and exits with code 1. expected-output/FIELDS.md
lists exactly which fields must appear.
Validation steps
- Run
python3 examples/io_demo.py 5 7and confirm the aligned report above. - Run
echo "5 7" | python3 examples/io_demo.pyand confirm identical output. - Run
echo "5 hello" | python3 examples/io_demo.py; echo $?and confirm anerror:line and an exit code of1. - Complete
starter/io_demo.py(replace everyFILL_ME_IN) so it matches the reference, then run the tests.
Tests
bash tests/run_tests.sh
Expected final line: 19 checks, 0 failure(s). while the starter is unfinished
(4 concept checks, 11 strict checks on the reference, 4 structural checks on the
starter), rising to 26 checks, 0 failure(s). once your starter passes strict
mode. The command exits 0 on success and non-zero on any failure, so it can run
in CI. It is fully non-interactive — every input is supplied via a pipe or an
argument.
Cleanup
Nothing to clean up: the program writes no files and makes no network calls. To
reset your work, restore the starter from git:
git checkout -- starter/io_demo.py.
Troubleshooting
See troubleshooting.md for the full list (string-vs-number
conversion, the hanging-input() trap, testing interactive programs via pipes,
stderr routing, exit codes, ragged columns, Windows notes).
Security notes
See security.md. Short version: never eval() input, validate and
convert everything, and think before printing sensitive values. The scripts make
no network calls, write no files, and need no elevated privileges.
Extension exercises
- Make
io_demo.pya streaming filter: readsys.stdinline by line, treat each line as onea bpair, and print one formatted result row per line soprintf '5 7\n2 3\n' | python3 io_demo.pyprints two rows. - Add a
,thousands separator to the sum column so1000000 2000000prints its sum as3,000,000.00. - Make a malformed line print a warning to stderr and be skipped, without aborting the whole run — one bad line should not lose the good ones.
Navigation
- Previous day: Day 46 — Numbers, Math, and Precision
(
labs/sections/programming-with-python/day-046-numbers-math-and-precision/). - Next day: Day 48 — Reading Error Messages and Debugging
(
labs/sections/programming-with-python/day-048-reading-error-messages-and-debugging/, to be written).
Expected output
FIELDS.md
# Required output fields (all platforms)
`sample-run.txt` in this directory is a real captured run on the authoring
machine (macOS, Python 3.14, 2026-07-12). The behavior is identical on Linux
and inside WSL — Python's formatting and stream handling are the same
everywhere; only the shell prompt (`$`) may look different.
## On good input (`5 7`, by argument or by pipe)
A correct run of `io_demo.py` prints, on **standard output**, in order:
1. `Input values`
2. ` a` followed by the first number, right-aligned, two decimals (`5.00`)
3. ` b` followed by the second number, right-aligned, two decimals (`7.00`)
4. `Results`
5. ` sum` followed by the sum, two decimals (`12.00`)
6. ` product` followed by the product, two decimals (`35.00`)
7. ` mean` followed by the mean, two decimals (`6.00`)
The two number columns are right-aligned to a fixed width, so the decimal
points line up regardless of how many digits each value has. Argument mode
(`python3 io_demo.py 5 7`) and pipe mode (`echo "5 7" | python3 io_demo.py`)
produce byte-for-byte identical output.
## On bad input (e.g. `5 hello`, or only one number)
- **Nothing** is printed to standard output.
- A single line beginning `error:` is printed to **standard error**.
- The process exits with a **non-zero** code (`1`); `echo $?` confirms it.
This split — results on stdout, errors on stderr, non-zero exit on failure — is
what lets the tool sit safely in a shell pipeline.
sample-run.txt
$ python3 examples/io_demo.py 5 7
Input values
a 5.00
b 7.00
Results
sum 12.00
product 35.00
mean 6.00
$ echo "5 7" | python3 examples/io_demo.py
Input values
a 5.00
b 7.00
Results
sum 12.00
product 35.00
mean 6.00
$ echo "5 hello" | python3 examples/io_demo.py
error: both inputs must be numbers (got '5 hello')
$ echo $?
1
Source files
examples/io_demo.py (2339 bytes)
#!/usr/bin/env python3
"""Day 047 lab — a friendly CLI greeter/calculator (reference solution).
Reads two numbers EITHER from a command-line argument OR from standard input,
so it is testable non-interactively:
python3 io_demo.py 5 7
echo "5 7" | python3 io_demo.py
It converts the input safely (a clear error on bad input), computes a small
stats line, and prints a nicely f-string-formatted, aligned report. On bad
input it writes a message to STANDARD ERROR and exits with a non-zero code, so
the failure is visible to a shell or a calling script.
"""
import sys
def read_raw() -> str:
"""Return the raw input text: the command-line argument if given,
otherwise everything piped in on standard input."""
if len(sys.argv) > 1:
# Join all positional args so both `io_demo.py 5 7` and
# `io_demo.py "5 7"` work.
return " ".join(sys.argv[1:])
return sys.stdin.read()
def parse_pair(raw: str) -> tuple[float, float]:
"""Split the raw text into exactly two numbers, converting safely.
Raises ValueError with a clear message if there are not exactly two
whitespace-separated tokens or if either token is not a number.
"""
parts = raw.split()
if len(parts) != 2:
raise ValueError(f"expected two numbers, got {len(parts)}")
try:
return float(parts[0]), float(parts[1])
except ValueError:
# Re-raise with the original text so the message is actionable.
raise ValueError(f"both inputs must be numbers (got {raw.strip()!r})")
def format_report(a: float, b: float) -> str:
"""Build the aligned, f-string-formatted report as one string."""
total = a + b
product = a * b
mean = total / 2
lines = [
"Input values",
f" {'a':<9}{a:>8.2f}",
f" {'b':<9}{b:>8.2f}",
"Results",
f" {'sum':<9}{total:>8.2f}",
f" {'product':<9}{product:>8.2f}",
f" {'mean':<9}{mean:>8.2f}",
]
return "\n".join(lines)
def main() -> int:
raw = read_raw()
try:
a, b = parse_pair(raw)
except ValueError as err:
# Diagnostics go to STDERR, never stdout, so a pipeline stays clean.
print(f"error: {err}", file=sys.stderr)
return 1
print(format_report(a, b))
return 0
if __name__ == "__main__":
sys.exit(main())
metadata.yml (621 bytes)
lesson_id: D047
day: 47
kind: python-program
languages: [python]
setup_commands:
- cd labs/sections/programming-with-python/day-047-input-output-and-f-strings
run_commands:
- python3 examples/io_demo.py 5 7
- echo "5 7" | python3 examples/io_demo.py
- python3 starter/io_demo.py 8 3
test_commands:
- bash tests/run_tests.sh
cleanup_commands:
- 'git checkout -- starter/io_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, bash tests/run_tests.sh → 19 checks, 0 failures'
requirements/README.md (1134 bytes)
# Dependencies — Day 047 lab
**Only Python 3 and a POSIX shell.** This lab has no installable dependencies:
- `python3` (version 3.6 or newer — f-strings and their format specs work on
every supported Python; the authoring machine used Python 3.14). Check with
`python3 --version`.
- `bash` (preinstalled on macOS and every mainstream Linux distribution) to run
the test script.
- Standard shell utilities used by the tests only: `echo`, `printf`, `grep`,
`mktemp` — all part of the base system.
The program itself imports only `sys` from the Python standard library, so there
is deliberately no `requirements.txt` here. Everything runs offline, needs no
account or API key, and needs no elevated privileges.
## Installing Python (if needed)
- **macOS:** `brew install python` (Homebrew) or download from the official
python.org installer. macOS may already ship a `python3`.
- **Linux:** `sudo apt install python3` (Debian/Ubuntu) or the equivalent for
your distribution; most ship Python 3 already.
- **Windows:** install Python from the Microsoft Store or python.org, or use WSL
and follow the Linux path.
starter/io_demo.py (3164 bytes)
#!/usr/bin/env python3
"""Day 047 lab starter — complete the five numbered exercises below.
Goal: read two numbers (from a command-line argument OR from standard input),
convert them safely, and print an aligned, f-string-formatted report. On bad
input, write an error to STANDARD ERROR and exit with a non-zero code.
See the finished target first:
python3 examples/io_demo.py 5 7
echo "5 7" | python3 examples/io_demo.py
Then complete each exercise, replacing every "FILL_ME_IN", and run:
bash tests/run_tests.sh
The sentinel FILL_ME_IN below is how the test knows the lab is unfinished.
"""
import sys
FILL_ME_IN = "FILL_ME_IN"
def read_raw() -> str:
"""Exercise 1 — read the input from EITHER the command line OR stdin.
If arguments were given (len(sys.argv) > 1), join sys.argv[1:] with a
single space and return that string. Otherwise, return sys.stdin.read().
Reading both ways is what makes the program testable without a keyboard.
Replace the return below with that logic.
"""
return FILL_ME_IN
def parse_pair(raw: str) -> tuple[float, float]:
"""Exercises 2 and 4 — turn the raw text into exactly two numbers, safely.
Exercise 2 (convert): split raw into tokens with raw.split(); if there are
not exactly two tokens, raise ValueError(f"expected two numbers, got
{len(parts)}").
Exercise 4 (handle bad input): convert with float(parts[0]) and
float(parts[1]) inside a try/except ValueError, and in the except branch
raise ValueError(f"both inputs must be numbers (got {raw.strip()!r})").
Remember: input text is a str — float() is what makes it a number.
"""
parts = raw.split()
# ... your Exercise 2 length check goes here ...
# ... your Exercise 4 try/except conversion goes here ...
return float(parts[0]), float(parts[1]) # replace with the safe version
def format_report(a: float, b: float) -> str:
"""Exercise 3 — format each value right-aligned to width 8, two decimals.
Each value below currently prints with no formatting. Give every number an
f-string spec of the form {value:>8.2f} so the two columns line up and show
exactly two decimal places (compare against examples/io_demo.py).
"""
total = a + b
product = a * b
mean = total / 2
lines = [
"Input values",
f" {'a':<9}{a}", # replace {a} with {a:>8.2f}
f" {'b':<9}{b}", # replace {b} with {b:>8.2f}
"Results",
f" {'sum':<9}{total}", # replace {total} with the aligned spec
f" {'product':<9}{product}",
f" {'mean':<9}{mean}",
]
return "\n".join(lines)
def main() -> int:
raw = read_raw()
try:
a, b = parse_pair(raw)
except ValueError as err:
# Exercise 5 — write the error to STANDARD ERROR (not stdout) so it
# never pollutes a pipeline, then signal failure with a non-zero code.
# Add file=sys.stderr to the print below.
print(f"error: {err}") # <- add file=sys.stderr
return 1
print(format_report(a, b))
return 0
if __name__ == "__main__":
sys.exit(main())
starter/io-worksheet.md (2282 bytes)
# Day 047 worksheet — Input, Output, and f-strings
Fill this in from your own runs as you complete `io_demo.py`. Copy real values
straight from your terminal — the point is to *see* the behavior, not guess it.
## 1. Proof that `input()` returns a string
Run this in a Python shell (`python3`), type `5` when prompted, and record what
you see:
```python
x = input("x: ")
type(x) # what does this report? __________________
x # what value is x? __________________
x + x # what does this print? __________________ (why is it not 10?)
```
- `type(input("x: "))` reports: `______________________`
- Typing `5` gives the value: `______________________`
- One sentence, in your own words, on why `input()` returns text and not a number:
`______________________________________________________________________`
## 2. The format spec you used
Write the exact f-string spec you used to right-align a number to two decimals
in a fixed-width column, and label each part:
- Spec: `f"{value:__________}"`
- The alignment character is `____` and it means `______________________`
- The width is `____` and it means `______________________`
- The `.2f` part means `______________________`
## 3. What your finished program prints
Run your completed program on the input `8 3` **both ways** and paste the output:
```text
$ python3 starter/io_demo.py 8 3
______________________________________________
______________________________________________
______________________________________________
______________________________________________
______________________________________________
______________________________________________
$ echo "8 3" | python3 starter/io_demo.py
(should be identical to the line above)
```
## 4. Why errors go to standard error
In two or three sentences, explain why `io_demo.py` prints its error message to
**standard error** instead of standard output, and what would go wrong in a
pipeline like `python3 io_demo.py | sort` if the error went to standard output
instead:
```
____________________________________________________________________________
____________________________________________________________________________
____________________________________________________________________________
```
tests/run_tests.sh (5505 bytes)
#!/usr/bin/env bash
# Tests for the Day 047 lab. Run from the lab directory:
# bash tests/run_tests.sh
#
# Verifies that io_demo.py reads a pair of numbers from EITHER a command-line
# argument OR standard input, prints an aligned, two-decimal report, and — on
# bad input — writes a message to STDERR and exits non-zero. The reference is
# always held to a strict behavioral standard. The learner's starter is checked
# strictly once every FILL_ME_IN placeholder is gone, and structurally (does it
# still parse, does it still contain the scaffolding) before that.
#
# No network, no interactivity: every input is supplied via arg or pipe.
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
}
# Full behavioral checks — run against any COMPLETE program.
run_strict_checks() {
local script="$1" arg_out stdin_out bad_code bad_stderr bad_stdout tmp_err tmp_out
echo "Testing ${script} (strict behavior) ..."
tmp_err="$(mktemp)"
tmp_out="$(mktemp)"
# 1. Argument mode: `io_demo.py 5 7` exits 0.
if arg_out="$(python3 "${script}" 5 7 2>/dev/null)"; then
check "runs with command-line arguments (exit 0)" "yes"
else
check "runs with command-line arguments (exit 0)" "no"
fi
# 2. Stdin mode: `echo "5 7" | io_demo.py` exits 0.
if stdin_out="$(echo "5 7" | python3 "${script}" 2>/dev/null)"; then
check "runs with piped stdin (exit 0)" "yes"
else
check "runs with piped stdin (exit 0)" "no"
fi
# 3. The two input modes must agree.
if [ "${arg_out}" = "${stdin_out}" ]; then
check "argument and stdin modes produce identical output" "yes"
else
check "argument and stdin modes produce identical output" "no"
fi
# 4. Report structure and aligned, two-decimal values.
echo "${arg_out}" | grep -q '^Input values$' && check "prints 'Input values' header" "yes" || check "prints 'Input values' header" "no"
echo "${arg_out}" | grep -q '^Results$' && check "prints 'Results' header" "yes" || check "prints 'Results' header" "no"
echo "${arg_out}" | grep -qE '(^| )12\.00$' && check "sum of 5 and 7 shown as 12.00 (two decimals)" "yes" || check "sum of 5 and 7 shown as 12.00 (two decimals)" "no"
echo "${arg_out}" | grep -qE '(^| )35\.00$' && check "product shown as 35.00" "yes" || check "product shown as 35.00" "no"
echo "${arg_out}" | grep -qE '(^| )6\.00$' && check "mean shown as 6.00" "yes" || check "mean shown as 6.00" "no"
# 5. Bad input -> non-zero exit, a message on STDERR, and nothing on stdout.
echo "5 hello" | python3 "${script}" 1>"${tmp_out}" 2>"${tmp_err}"
bad_code=$?
bad_stderr="$(cat "${tmp_err}")"
bad_stdout="$(cat "${tmp_out}")"
rm -f "${tmp_err}" "${tmp_out}"
[ "${bad_code}" -ne 0 ] && check "bad input exits non-zero" "yes" || check "bad input exits non-zero" "no"
[ -n "${bad_stderr}" ] && check "bad input writes a message to stderr" "yes" || check "bad input writes a message to stderr" "no"
[ -z "${bad_stdout}" ] && check "bad input prints nothing to stdout" "yes" || check "bad input prints nothing to stdout" "no"
}
# Structural checks — run against an UNFINISHED starter.
run_structural_checks() {
local script="$1"
echo "Testing ${script} (structure only) ..."
python3 -c 'import sys; compile(open(sys.argv[1]).read(), sys.argv[1], "exec")' "${script}" 2>/dev/null && check "starter is valid Python (parses)" "yes" || check "starter is valid Python (parses)" "no"
grep -q 'sys.argv' "${script}" && check "starter references sys.argv (reading an argument)" "yes" || check "starter references sys.argv (reading an argument)" "no"
grep -q 'sys.stdin' "${script}" && check "starter references sys.stdin (reading a pipe)" "yes" || check "starter references sys.stdin (reading a pipe)" "no"
grep -q 'sys.stderr' "${script}" && check "starter references sys.stderr (error channel)" "yes" || check "starter references sys.stderr (error channel)" "no"
}
# --- python -c sanity checks (the concepts the lab teaches) ---------------
echo "Concept checks (python3 -c) ..."
printf '5\n' | python3 -c 'import sys; assert type(sys.stdin.readline().rstrip("\n")) is str' && check "a line read from input is a str" "yes" || check "a line read from input is a str" "no"
python3 -c 'assert f"{7.5:>10.2f}" == " 7.50"' && check "f-string {7.5:>10.2f} right-aligns to width 10" "yes" || check "f-string {7.5:>10.2f} right-aligns to width 10" "no"
python3 -c 'assert f"{1234567.891:,.2f}" == "1,234,567.89"' && check "f-string thousands separator works" "yes" || check "f-string thousands separator works" "no"
python3 -c 'assert "5" + "7" == "57" and int("5") + int("7") == 12' && check "converting input turns concatenation into addition" "yes" || check "converting input turns concatenation into addition" "no"
echo
run_strict_checks "${lab_dir}/examples/io_demo.py"
echo
# The starter ships with FILL_ME_IN placeholders. Once the learner has replaced
# every one, hold their script to the same strict standard.
if grep -q 'FILL_ME_IN' "${lab_dir}/starter/io_demo.py"; then
echo "Note: starter/io_demo.py still has unfinished exercises (FILL_ME_IN)."
run_structural_checks "${lab_dir}/starter/io_demo.py"
else
run_strict_checks "${lab_dir}/starter/io_demo.py"
fi
echo
echo "${checks} checks, ${failures} failure(s)."
[ "${failures}" -eq 0 ]
Troubleshooting
Troubleshooting — Day 047 lab
The two numbers concatenate instead of adding (I get 57, not 12)
input() and every item in sys.argv are strings, so "5" + "7" glues
them into "57". Convert each to a number first with float(...) (or int(...)
for whole numbers) before doing arithmetic. This is the single most common
beginner bug in Python, and the whole reason parse_pair calls float().
The program hangs and prints nothing
You probably called input() in a context with no keyboard. When a program is
run inside an automated test or a pipeline with nothing piped in, input()
waits forever for a human who is not there. This lab avoids the trap by reading
sys.argv when an argument is present and sys.stdin otherwise — neither of
which blocks on a missing keyboard. If you press Ctrl+D (macOS/Linux) it sends
"end of input" and unblocks a waiting sys.stdin.read().
Testing an interactive program without typing
You do not need to type anything to test a program that reads standard input — feed it with a pipe or a here-string:
echo "5 7" | python3 examples/io_demo.py # pipe
python3 examples/io_demo.py <<< "5 7" # bash here-string
printf '5 7\n' | python3 examples/io_demo.py # printf for exact bytes
python3 examples/io_demo.py 5 7 # or just pass arguments
This is exactly how the test script drives the program, which is why the tests are fully non-interactive.
My error message shows up mixed into the data / gets captured by a pipe
You printed the error to standard output. Diagnostics must go to standard error:
print(f"error: {err}", file=sys.stderr). To watch the two channels separately:
python3 examples/io_demo.py 5 hello 1>/tmp/out.txt 2>/tmp/err.txt
cat /tmp/out.txt # should be empty on bad input
cat /tmp/err.txt # the error line
echo $? shows 0 after bad input
You reported the error but did not signal failure. Return a non-zero code:
call sys.exit(1) (or, as in the reference, return 1 from main() and
sys.exit(main())). Only a non-zero exit tells a shell or CI that the run
failed.
The columns are ragged / decimals do not line up
A number without a format spec prints at its natural width, so columns wander.
Give every value in a column the same fixed-width, fixed-decimal spec, e.g.
{value:>8.2f} — > right-aligns, 8 sets the width, .2f fixes two
decimals. Mixing widths between rows is the usual cause.
command not found: python3
Python 3 is not on your PATH. Install it (see requirements/README.md) or try
python --version — on some systems the command is python. Never assume;
check the version, because python sometimes still means Python 2.
Windows: bash is not recognized
Use WSL (wsl --install, then open Ubuntu and run the commands there), or run
the Python program directly in PowerShell (python examples\io_demo.py 5 7) and
skip the bash test script.
Security notes
Security notes — Day 047 lab
-
Never
eval()input. The most dangerous mistake withinput()(or withsys.argv, or with piped data) is passing it toeval(), which executes the text as Python code.eval(input())lets whoever supplies the input run any command on your machine — delete files, open a network connection, anything. This lab deliberately turns text into numbers withfloat(), which parses a value and nothing more. Whenever you need a number from input, reach forint()orfloat(), nevereval(). -
Validate and convert every input. Input is data, not instructions. The program checks that it received exactly two whitespace-separated tokens and that both convert to numbers, and it rejects anything else with a clear error. Treating all input as untrusted until validated is the habit behind every defense against injection attacks.
-
Mind what you print, and where. Output is where data leaves your program. Printing a secret for debugging is the classic way a password or key leaks into a terminal, a log, or a screen-share. Standard error is captured by logging systems just as readily as standard output, so "I only printed it to stderr" is no protection. Before every
print(), consider who can see that channel. -
What these scripts do: read a pair of numbers, compute a few statistics, and print them. They make no network connections, write no files, and change no settings. They need no elevated privileges — nothing here should ever require
sudo. -
Read before you run. Both
io_demo.pyfiles are short and commented; read them before executing. Running unread scripts is one of the most common ways developers get compromised, and this course's rule is that every lab file is small enough to read and understand first.