Today we crossed from writing code that works to writing code someone else can read, then opened up lists and tuples.
1. Naming: The Beginner/Professional Divide
- A function returning a boolean should read as a question:
is_prime, notnot_a_prime_number. - Names must be self-explanatory, never cryptic.
- You write code for two audiences: the machine and the next developer. Only one of them will complain.
Readability is the clearest marker separating a beginner from a professional.
2. Structuring Code into Modules
We moved our utilities into numeric_utils.py and started it with a module docstring at the top, stating what the module is for. One file, one clear purpose.
3. The Utilities We Built
def is_even_number(number: int) -> bool:
"""Return True if the number is even."""
return number % 2 == 0
def is_prime(number: int) -> bool:
"""Return True if the number is prime."""
if number < 2: # handles 0, 1 and negatives
return False
i = 2
while i < number:
if number % i == 0:
return False
i += 1
return True
This closes yesterday’s edge-case exercise. The original version called 1 prime, because the loop never ran. The guard if number < 2 fixes 0, 1 and every negative in one line.
4. Testing Your Own Code
We created a separate test_utils.py so tests don’t clutter the module, and ran them under if __name__ == "__main__":.
Rather than printing results and squinting at them, let the computer check:
if __name__ == "__main__":
assert is_prime(7) is True
assert is_prime(1) is False
assert is_prime(-7) is False
print("all tests passed")
An assert stays silent when correct and fails loudly when not. That’s exactly what you want. (Later we’ll move these into pytest.)
5. Default, Positional and Keyword Arguments
def sum_of_multiples_of_three_or_five(start=1, end=10):
...
sum_of_multiples_of_three_or_five() # uses the defaults
sum_of_multiples_of_three_or_five(1, 1000) # positional: order matters
sum_of_multiples_of_three_or_five(end=1000) # keyword: order doesn't
- Default arguments let the function run with minimal input.
- Positional: matched by order.
- Keyword: matched by name, so order is free.
- In a call, positional arguments must come first.
f(end=1000, 1)is a syntax error. - Never use a mutable default such as
def f(items=[]). That list is created once and shared across every call. UseNoneand build it inside instead.
6. Sequence Types
A sequence holds multiple values in order, indexed from 0.
| Type | Mutable? |
|---|---|
| List | Yes, change and append freely |
| Tuple | No, fixed once created |
| String | No, also a sequence, also immutable |
Strings count as sequences because you can reach a character by index. But s[0] = "H" raises a TypeError, exactly like a tuple.
7. Indexing and Slicing
items = ["a", "b", "c", "d"]
items[0] # "a" first
items[-1] # "d" last
items[-2] # "c" second from last
items[1:3] # ["b", "c"] from 1 up to, not including, 3
Negative indexing saves you computing len(x) - 1 every time. The same idea shows up well beyond Python: in Terraform, for instance, it’s how you grab the last resource in a list.
8. Coming Soon: Comprehensions
A comprehension compresses a loop and its logic into a single expression. It looks intimidating now and won’t in two weeks. Learn the loop properly first; the short form is a reward, not a shortcut.
✅ Action Items
- Write two or three original functions of your own using today’s ideas: clear names, a docstring, sensible defaults.
- Add
asserttests for each one, including the awkward inputs. - Review the notes on directai.blog.
