FreeShort Course

Advanced Python Programming

Basic tier: containers, guarded class state and protocols. Advanced tier: defensive code, data pipelines and real APIs - eight modules culminating in the TaskFlow capstone.

FreeNo fee
8 weeks20 hours
Short CourseBeginner to Intermediate
OnlineLive & instructor-led

About this course

Basic tier: containers, guarded class state and protocols. Advanced tier: defensive code, data pipelines and real APIs - eight modules culminating in the TaskFlow capstone.

This is a 6-week short course - enough depth to build real projects and a portfolio piece, without a long commitment. It runs online in the August 2026 cohort (starting 1 August 2026) and is taught the EchoLens way: you learn by doing real, gradeable work rather than just watching lectures.

What's included

  • Live, instructor-led online sessions across 8 weeks (20 hours total).
  • Hands-on coding quests you solve inside the EchoLens browser compiler - nothing to install.
  • Gems, stages and a leaderboard that keep you moving instead of grade anxiety.
  • A verified certificate with a scannable QR code, ready to share on LinkedIn, when you finish.
  • Completely free - no fee, just create an account and start.

What you will learn

Dictionaries & setsProperties & guarded stateDunder methods & protocolsRecursion & backtrackingException hierarchiesGenerators & context managersSchema migrationResilient API clientsTesting & packaging

Course outline - level by level

8 leveles, each with hands-on quests you clear in the portal.

  • Level 1. Basic 1: Dictionaries, Sets and Choosing a Container - A dictionary is a hash table: lookup by key is constant on average and keys must be hashable, therefore immutable. A set is the same machinery without values, turning membership testing from a linear scan into a constant time check - the single most common performance improvement in beginner Python. The correct container is chosen by asking one question: what is the access pattern. Key rules: - Dictionary and set lookup is constant on average. List membership testing is linear. - Keys must be hashable, therefore immutable - a list can never be a key, a tuple can. - Set algebra (union, intersection, difference, symmetric difference) replaces nested loops. - Counting occurrences is a dictionary of counts, or Counter from the standard library. Worked example - membership and counting done the right way: words = text.lower().split() stop = {"the", "and", "of", "a"} # constant time membership counts = {} for w in words: if w in stop: continue counts[w] = counts.get(w, 0) + 1
  • Level 2. Basic 2: Classes, Properties and Guarded State - Python has no private access, only conventions, which shifts the burden of protection onto design. The property decorator keeps an attribute's simple access syntax while gaining validation on write, so existing calling code never changes. Class attributes versus instance attributes is the other trap: a class attribute is shared by every instance, and a mutable one shared this way produces defects that look like haunting. Key rules: - A class attribute is shared by all instances - never make it mutable unless sharing is the intent. - The property decorator adds validation without changing the attribute access syntax used by callers. - A single leading underscore is a convention meaning internal - nothing enforces it, so document the contract. - Define a readable string representation for every class you will debug, and a precise one for developers. Worked example - a property that guards a state transition: class Task: VALID = {"todo", "doing", "done"} def __init__(self, title): self.title = title; self._status = "todo" @property def status(self): return self._status @status.setter def status(self, value): if value not in self.VALID: raise ValueError(f"unknown status: {value}") self._status = value
  • Level 3. Basic 3: Dunder Methods and Protocol Design - Python is built on protocols rather than interfaces: an object is iterable because it implements the iteration protocol, sortable because it implements comparison, printable because it implements string conversion. Two rules are easy to miss: equality and hashing must agree, and the developer representation should ideally be text that recreates the object. Key rules: - Implement the string method for users and the representation method for developers. - If two objects compare equal they must hash equal - define both together or neither. - Implementing less-than is enough for sorting; the remaining comparisons can be generated. - Implementing iteration lets your object work with loops, comprehensions and the whole standard library. Worked example - a polynomial type that sorts, prints and adds natively: class Poly: def __init__(self, coeffs): self.c = list(coeffs) def __repr__(self): return f"Poly({self.c})" def __eq__(self, o): return isinstance(o, Poly) and self.c == o.c def __hash__(self): return hash(tuple(self.c)) def __lt__(self, o): return self.degree() < o.degree() def degree(self): return len(self.c) - 1
  • Level 4. Basic 4: Recursion, Backtracking and Divide and Conquer - A recursive solution has three parts: a base case that stops, a recursive case that reduces the problem, and a guarantee that repeated reduction reaches the base. Backtracking adds a fourth: undo the choice when the branch fails - that undo step turns brute force into something that finishes, especially once a pruning test rejects hopeless branches early. Key rules: - Every recursion needs a base case and a strictly reducing step. - Merge sort runs in n log n time and needs order n extra space. - Backtracking is choose, recurse, undo - the undo step is not optional. - A pruning test that rejects a branch early is usually worth more than any constant factor optimisation. Worked example - backtracking with an explicit undo step: def solve(board, row, n): if row == n: return True for col in range(n): if safe(board, row, col): board[row] = col # choose if solve(board, row + 1, n): return True board[row] = -1 # undo return False
  • Level 5. Advanced 1: Exceptions, Contracts and Failing Well - Exception handling is a design activity, not a safety net bolted on at the end. A bare handler that catches everything converts a crash into silent wrongness, which is strictly worse. The professional pattern: narrow handlers close to the operation that can fail, custom exception types that carry the context a caller needs to decide, and a clear boundary where errors stop being handled and start being reported. Key rules: - Catch the narrowest exception type that can occur - never catch everything without re-raising. - The else clause runs when no exception occurred; finally always runs, including on return. - Custom exception types carry context - a message alone forces the caller to parse text. - An assertion documents an assumption for developers; it is not input validation. Worked example - a narrow contract with a typed failure: class ConfigError(Exception): def __init__(self, key, reason): super().__init__(f"{key}: {reason}") self.key, self.reason = key, reason def read_port(cfg): try: port = int(cfg["port"]) except KeyError: raise ConfigError("port", "missing") except ValueError: raise ConfigError("port", "not an integer") return port
  • Level 6. Advanced 2: Generators, Context Managers and Streaming Data - A generator produces values one at a time and remembers where it stopped, which means a pipeline of generators processes a file of any size in constant memory. Context managers guarantee that a resource is released on every exit path including exceptions, which is why the with statement is not optional for file handling. Key rules: - A generator holds one item at a time - memory use stays flat regardless of input size. - Generators are consumed once - iterate again and you get nothing. - Always open files with a context manager - it closes on the exception path too. - Chain generators to build a pipeline; each stage stays a small, testable function. Worked example - a three stage streaming pipeline over a large log: def lines(path): with open(path, encoding="utf-8") as f: for line in f: yield line.rstrip("\n") def errors(rows): for r in rows: if " ERROR " in r: yield r
  • Level 7. Advanced 3: Structured Persistence, Schema Migration and APIs - Any application that stores data will eventually change its shape, and the moment that happens the file written by the old version becomes a liability. Writing a version number into the file from day one, and a small migration function per version step, converts that liability into a routine upgrade. Calling a network API needs a timeout, a retry policy and a response check - all mandatory rather than optional. Key rules: - Write a schema version into every stored file - migration is a chain of small steps. - Every network call gets an explicit timeout. - Retry only on transient failures, with an increasing wait, and cap the number of attempts. - Validate the response shape before using it. Worked example - versioned storage with a migration chain: MIGRATIONS = { 1: lambda d: {**d, "tags": [], "version": 2}, 2: lambda d: {**d, "archived": False, "version": 3}, } def load(path): with open(path, encoding="utf-8") as f: data = json.load(f) while data.get("version", 1) in MIGRATIONS: data = MIGRATIONS[data["version"]](data) return data
  • Level 8. Advanced 4: Testing, Packaging and the Course Capstone - Tests are not about proving code correct, they are about making change safe. A test suite that runs in seconds and fails loudly when behaviour changes is what allows a project to be refactored at all. Test the boundary cases and the error paths rather than the happy path. Packaging closes the loop by making the work runnable by someone other than its author. Key rules: - Test the boundaries and the failure paths - the happy path is the least likely place for defects. - Each test must be independent. - Coverage measures which lines ran, not whether behaviour is correct. - A project someone else cannot install and run in one command is not finished. Worked example - boundary focused tests rather than happy path tests: import pytest from tasks import Task def test_rejects_unknown_status(): t = Task("write report") with pytest.raises(ValueError): t.status = "finished"

How you submit: Coding quests solved in the built-in EchoLens compiler.

Who it's for

Advanced Python Programming suits learners at a beginner to intermediate level who want a practical, project-based route into Advanced Python Programming. You need only a browser and an internet connection - all coding runs inside the EchoLens compiler, so there is nothing to set up.

Certificate

Finish every stage and EchoLens issues a verified certificate carrying a QR code anyone can scan to confirm it on our site. You can add it to your CV or share it to LinkedIn in one click.

More Short Courses

Python for Data ScienceRs 12,500 · 6 weeksGenerative AI EssentialsRs 14,000 · 6 weeksData Analytics with SQL & Power BIRs 13,500 · 6 weeksIntroduction to Machine LearningRs 13,000 · 6 weeks