S
Saurav Danej
90-Day AI/ML LinkedIn Content System
← All days
5
Day 5 of 90Python

Loops & control flow — the Python way

POST 1 of 5 MorningPythonConcept

Stop writing C-style loops in Python

I can spot a developer who came to Python from Java or C in two seconds, just by looking at their loops.

for i in range(len(lst)):
    print(lst[i])

That's a C loop in Python clothing. It works, but it's writing C with extra steps. The Pythonic version is one line shorter and impossible to off-by-one:

for item in lst:
    print(item)

Need the index too? Use enumerate. for i, item in enumerate(lst): gives you both. Don't reach for range(len()) — that's a habit from languages where you HAD to track indices. Python iterates over iterables natively.

Need two parallel lists? zip is the answer. for a, b in zip(xs, ys): gives you pairs. Python 3.10 added zip(xs, ys, strict=True), which raises if the two iterables have different lengths. Use it. Silent zip-truncation is a bug class.

Need to walk over chunks of a list? itertools.batched(lst, n) (Python 3.12+) yields tuples of n consecutive items. Before 3.12, you wrote a manual chunker. After, you don't.

The broader principle. Python's for-loop iterates over *values*, not over an integer index that you then use to retrieve values. Once you internalise this, your loops shrink, your bug surface shrinks, and your code reads like English.

The rare exception is when you genuinely need the index for arithmetic — e.g., comparing element i with element i+1. Even then, enumerate plus a sentinel is usually cleaner than range(len()).

It's a small habit. The amount of grief it saves a Python codebase, over years, is enormous. Break the C-style reflex.
#AI#MachineLearning#Python#100DaysOfCode#BuildInPublic#PythonStyle#PythonBasics
POST 2 of 5 MiddayPythonDeep dive

The loop-else clause — Python's best-kept secret

Python has a feature so obscure that some Python developers I've worked with for years didn't know it existed. It's called the loop-else clause, and once you start using it, you wonder how you ever wrote search loops without it.

for item in items:
    if matches(item):
        return item
else:
    raise NotFound()

The else clause attached to a for-loop runs ONLY if the loop completed without hitting a break (or an early return, in this case). It's a clean way to say 'I searched the whole iterable and didn't find what I was looking for'.

The alternative — the dance most developers do — is a found = False flag. Set it inside the if, check it after the loop. Six lines, two state variables, three places to make a mistake. The for/else does the same thing in zero extra state.

Works the same on while-loops. while not done: ... else: <runs only if condition went False without a break>.

The naming is admittedly bad. 'else' here means 'else after the loop', not 'else if the loop didn't run'. Guido has publicly said he'd name it 'nobreak' if he could go back. The keyword is what we have, though, and it's standard Python.

One caveat — if you're new on a team, leave a one-line comment the first time you use it. Half your reviewers will ask 'wait, when does that else fire?' Better to head off the question than risk a 'is this a bug?' comment.

Once the team knows, they keep using it. Search loops, validation loops, retry loops — all cleaner with for/else. It's free in the language. Use it.
#AI#MachineLearning#Python#100DaysOfCode#BuildInPublic#PythonAdvanced#PythonHidden
POST 3 of 5 AfternoonPythonCode

match-case — better than a chain of ifs

Python 3.10 added structural pattern matching with match/case. Most articles online still describe it as 'switch with new syntax'. That undersells it badly. It's pattern matching, not switching — it can deconstruct dicts, lists, tuples, and even your own classes.

Look at the snippet. Each case isn't checking equality — it's matching the *shape* of the value. The first case matches dicts that have a 'type' of 'click' and binds x and y from the dict's structure. The second matches scroll events with a 'dy'. The third matches anything with a string-typed 'type' and binds it. The wildcard _ catches everything else.

This kind of dispatching, before 3.10, was an isinstance ladder or a dict-of-functions. Pattern matching makes it readable as English. Each case is a spec, not a check.

Pattern matching also supports guards — case [first, *rest] if first.startswith('http'): runs only if the list-pattern AND the boolean condition both match. It supports class patterns — case Point(x=0, y=y): matches a Point with x=0, binding y. It supports literal-or — case 'GET' | 'HEAD':.

When NOT to use it. If your branches are simple equality checks on a single variable, an if/elif chain is just as clear and avoids the new syntax. If your branches are heterogeneous in what they consume — some look at the type, some look at a field, some look at length — pattern matching shines.

Rule of thumb: if you find yourself writing isinstance() three or more times in a row, or destructuring a dict's fields by hand, switch to match/case.

The code reads like a specification. The interpreter does the heavy lifting.
#AI#MachineLearning#Python#100DaysOfCode#BuildInPublic#Python310#PythonAdvanced
POST 4 of 5 EveningPythonTip

The walrus operator — when it earns its keep

Python 3.8 added the walrus operator, := (so named because the colon-equals looks like a walrus's eyes and tusks). It assigns and returns a value in a single expression.

Most code shouldn't use it. Most uses I've seen on the internet are 'saving a line' in places where saving the line costs readability. The walrus is a power tool — useful, but easy to overdo.

There are two cases where it genuinely earns its place.

Case one — avoiding recomputation in a comprehension. Suppose you have an expensive function expensive(x) and you want to keep only positives. Without walrus, you compute it twice or write a for-loop. With walrus, [y for x in data if (y := expensive(x)) > 0] computes once and filters in one pass.

Case two — read-and-test in a while loop. Reading a stream in chunks until empty:

while chunk := stream.read(4096):
    process(chunk)

The assignment happens, the result is tested, the loop continues until read() returns falsy. Without walrus, you write a 'while True / read / break' dance. With walrus, the intent is in the loop header.

That's it. Two cases. If you're tempted to use walrus to 'save a line' anywhere else — in an if-else, in a regular assignment, in a function call — don't. The savings aren't worth the cognitive cost.

A useful self-check. If a colleague asks 'why is this a walrus?' and your answer is 'to save a line', it shouldn't be a walrus. If your answer is 'to avoid double computation' or 'to read-and-test in a single expression', it's correct.

Power tools. Sparingly.
#AI#MachineLearning#Python#100DaysOfCode#BuildInPublic#PythonTips#Python38
POST 5 of 5 NightPythonRecap

Day 5 — loops you don't manage by hand

End of Day 5. Twenty-five posts shipped. The first week is more than half done.

A recap and a confession. Today's theme was control flow done the Python way, and writing about it forced me to rethink some of my own habits. I went back through a couple of recent personal projects after writing the morning post and found two range(len()) loops I should have written as enumerate. They've been merged.

What we covered.

Morning, why C-style loops are a tell — and how to replace them with Python's iteration primitives. for x in lst, enumerate, zip(strict=True), itertools.batched. Each one a small win; together, idiomatic Python.

Midday, the loop-else clause. Python's most underused feature. Eliminates the 'found = False' flag dance. Once you know it exists, you reach for it.

Afternoon, match/case for clean dispatch. Not a switch statement — pattern matching. Use when you're checking shape (dict-with-fields, class-with-attributes, list-with-prefix), not just equality.

Evening, the walrus operator's two real uses — recomputation in comprehensions, read-and-test in while loops. Two cases. Anywhere else, don't.

A broader theme is showing up across this week — Python is small at its core, but rich at the edges. The basics (variables, lists, dicts) are six chapters in any textbook. The 'feature' set (comprehensions, walrus, match/case, for/else) is what separates someone who writes Python from someone who *thinks* in Python.

Tomorrow, Day 6, we wrap the basics with functions. Default args (without the trap from Day 3), keyword-only args, *args/**kwargs, lambdas. Functions in Python are first-class — meaning every framework you'll touch (Flask, FastAPI, PyTorch) treats them as values. Tomorrow we make peace with that.

See you in the morning.
#AI#MachineLearning#Python#100DaysOfCode#BuildInPublic#100DaysPython#DailyRecap