Lesson 0: Python survival kit #
Why you care #
In SAS, you press F3 in a SAS session to run your program. In this course, you run script files from a terminal, and the scripts grade themselves with assertions. Everything in lessons 2–6 assumes the mechanics taught here.
Running a script #
A .py file is exactly like a .sas program — it's a text file containing Python code. To run it, activate the venv and call python path/to/file.py from the repo root.
Let's try it. Create a file called scratch.py in the repo root (not in any subdirectory) with exactly this line:
print("hello from python")
Then run:
python scratch.py
Output:
hello from python
When you're done, scratch.py is yours to delete; it's not part of the course and is ignored by git.
For platform-specific venv activation commands (macOS/Linux vs. Windows Git Bash), see the README section How to run the scripts.
Imports and aliases #
In SAS, you reference a library and give it a nickname (like PROC SQL; CREATE LIBRARY mylib = "path/to/data";). In Python, import polars as pl loads the Polars library and nicknames it pl so you can write pl.something instead of polars.something. This is just a convention — you can use any nickname, but pl for Polars is standard.
Create scratch.py:
import polars as pl
print(pl.__version__)
Run it:
python scratch.py
Output:
1.42.1
That's the Polars version installed in this repo.
Variables and print #
Assignment has no %LET or data-step split; name = value creates a variable, and print() displays any value.
Create scratch.py:
n = 42
name = "Alice"
print(n)
print(name)
Run it:
python scratch.py
Output:
42
Alice
f-strings #
An f-string is f"..." — the f prefix lets you embed expressions inside {}. This is like SAS PUT or %PUT statements, but more flexible. Inside {}, you can format numbers: {n:,} adds commas as thousands separators.
Create scratch.py:
n = 199613
print(f"rows: {n:,}")
Run it:
python scratch.py
Output:
rows: 199,613
Functions and the __main__ guard #
Every exercise in this course uses this shape:
def main() -> None:
# your work here
print("All checks passed.")
if __name__ == "__main__":
main()
Here's what each part does:
def main() -> None:defines a function calledmainthat takes no arguments and doesn't return anything (the-> Noneis just a label for clarity).if __name__ == "__main__": main()runsmain()only when the file is executed directly (not when another file imports it). This is trusted boilerplate — use it exactly as shown, and don't worry further about how it works.
Method chaining #
In SAS, you run PROC steps in sequence. In Polars, you chain method calls: df.filter(...).select(...) reads left to right like a pipeline. Each call returns a new DataFrame, so you can break chains across lines inside parentheses.
Create scratch.py:
import polars as pl
result = pl.DataFrame({"x": [1, 2, 3]}).filter(pl.col("x") > 1).height
print(result)
Run it:
python scratch.py
Output:
2
The chain: create a DataFrame → filter rows where x > 1 (leaves rows with x=2 and x=3) → get the row count (.height). Result: 2 rows.
How the course grades you: assertions and tracebacks #
Every exercise ends with assertions. When you run the file, Python checks each assert statement. If the condition is true, nothing happens. If it's false, Python raises AssertionError and stops. You then read the traceback (the error message) to find the problem and fix it.
How to read a traceback #
A traceback is read bottom-up. The last line names the error type and message. The lines above show where the error happened (file, line number, and code).
NotImplementedError: you haven't started the TODO yet #
Some exercises contain raise NotImplementedError("...") as placeholders for work you haven't done. When you run the file before filling in the blank, you get this.
Create scratch.py:
raise NotImplementedError("TODO 1: not started yet")
Run it:
python scratch.py
Output (read bottom-up):
Traceback (most recent call last):
File "scratch.py", line 1, in <module>
raise NotImplementedError("TODO 1: not started yet")
NotImplementedError: TODO 1: not started yet
The last line (NotImplementedError: TODO 1: not started yet) tells you: this TODO hasn't been started. The line before (raise NotImplementedError(...)) shows exactly where, and the file/line info (File "scratch.py", line 1) points you to it.
AssertionError: you did the TODO, but the result is wrong #
When an assertion fails, Python shows the code that failed and the custom message you wrote.
Create scratch.py:
x = 2 + 2
assert x == 5, f"expected 5, got {x}"
Run it:
python scratch.py
Output (read bottom-up):
Traceback (most recent call last):
File "scratch.py", line 2, in <module>
assert x == 5, f"expected 5, got {x}"
^^^^^^
AssertionError: expected 5, got 4
The last line (AssertionError: expected 5, got 4) tells you what you got wrong. The ^^^^^^ marker under the code shows exactly which part of the assertion failed.
NameError or AttributeError: you missed a blank #
Exercises 2–3 contain blanks: ____ appears where you need to fill in a variable name, method, or attribute. If you run before filling a blank, you get either NameError (for bare identifiers) or AttributeError (for method/attribute positions).
Create scratch.py:
print(____)
Run it:
python scratch.py
Output (read bottom-up):
Traceback (most recent call last):
File "scratch.py", line 1, in <module>
print(____)
^^^^
NameError: name '____' is not defined
The last line tells you a name isn't defined. The line before shows which name: ____. Go back to the exercise and replace ____ with the correct name.
Success: all assertions pass #
When every assert is true, you see All checks passed. and nothing else bad.
Create scratch.py:
x = 2 + 2
assert x == 4, f"expected 4, got {x}"
print("All checks passed.")
Run it:
python scratch.py
Output:
All checks passed.
Check yourself #
Question 1: What does this print?
print(f"{10_000:,}")
Answer
10,000
The {10_000:,} format adds commas as thousands separators. Underscores in numeric literals (like 10_000) are just visual separators in code — they don't change the number itself.
Question 2: What error do you get if you run a file whose first statement is raise NotImplementedError("TODO 2")?
Answer
Traceback (most recent call last):
File "scratch.py", line 1, in <module>
raise NotImplementedError("TODO 2")
NotImplementedError: TODO 2
The error type is NotImplementedError, with the message you provided. Read the traceback bottom-up: the last line tells you the error and its message.
Question 3: If you run assert 3 > 2, does it print anything?
Answer
No. The condition 3 > 2 is true, so the assertion passes silently. Assertions only produce output when they fail.
Next #
Next lesson: 01_parquet_in_vscode.md