Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Python Crash Course
Python is the language of simplicity and power. It’s the #1 choice for AI, Data Science, and rapid web development.This crash course takes you from “Hello World” to advanced topics like decorators, generators, and async programming.
Why Python?
Python’s philosophy is “Readability counts.” While other languages optimize for the machine, Python optimizes for the programmer’s brain. Code is read far more often than it is written, and Python embraces that reality. Think of Python as the “English of programming languages.” Just as English became the lingua franca of international business — not because it is the most precise or elegant language, but because it is widely understood and gets the job done — Python became the lingua franca of software because it lowers the barrier between thinking about a solution and expressing it in code. A data scientist, a web developer, and a DevOps engineer can all read each other’s Python. That is rare.Simple & Readable
Code reads like English. Focus on logic, not syntax.
AI & Data Dominance
The standard for Machine Learning (PyTorch, TensorFlow) and Data Science (Pandas, NumPy).
Versatile
Web apps (Django/FastAPI), scripts, automation, testing, and more.
Massive Ecosystem
PyPI has over 400,000 packages. “There’s a library for that.”
Course Roadmap
We will cover the “Pythonic” way of writing code.Fundamentals
Variables, types, control flow, and functions.
Start Learning
Data Structures
Lists, Dictionaries, Sets, and Tuples.
Master Data
Object-Oriented Programming
Classes, inheritance, magic methods, and dataclasses.
Explore OOP
Modules & Packages
Organizing code, virtual environments, and pip.
Organize Code
Advanced Python
Decorators, generators, context managers, and async/await.
Go Advanced
Prerequisites
- No prior programming experience required (though familiarity with any programming language will accelerate your learning).
- Python 3.10+ installed (
python --version). We use 3.10+ because it introduced structural pattern matching and cleaner union type syntax (X | Yinstead ofUnion[X, Y]). - A code editor (VS Code with the Python extension is recommended — it gives you autocompletion, linting, and debugging out of the box).
What “Pythonic” Actually Means
You will hear the word “Pythonic” constantly. It means more than just “written in Python.” Pythonic code leverages the language’s idioms and conventions to be readable, concise, and expressive. Writing Python like you would write Java or C++ is valid — it will run — but it misses the point. Throughout this course, we will call out Pythonic patterns explicitly so you develop the instinct. A quick example of the difference:The Zen of Python
Typeimport this in your Python shell:
Beautiful is better than ugly. Explicit is better than implicit. Simple is better than complex. … There should be one— and preferably only one —obvious way to do it.These are not just slogans. They are design principles baked into the language. When you are unsure whether to write something one way or another, the Zen is your tiebreaker. This course teaches you that “one obvious way.”
Interview Deep-Dive
Why is Python considered 'slow' compared to C++ or Go, and when does that actually matter in production?
Why is Python considered 'slow' compared to C++ or Go, and when does that actually matter in production?
Strong Answer:
- The headline answer is that CPython is an interpreted language running bytecode on a virtual machine, whereas C++ and Go compile directly to native machine code. That interpretation layer adds overhead on every instruction — typically 10-100x slower for raw CPU-bound computation.
- But “Python is slow” is one of the most misleading statements in software engineering. The real question is: slow at what? In most production systems, the bottleneck is I/O (database queries, network calls, disk reads), not CPU. A Django app serving API requests spends 95% of its time waiting on PostgreSQL, not crunching numbers. Python’s speed is irrelevant there — the database is the bottleneck.
- Where it genuinely matters is in tight computational loops: numerical simulation, image processing, real-time signal processing. But even there, the Pythonic answer is not “rewrite in C++” — it is to drop into C extensions. NumPy, Pandas, and PyTorch are all Python frontends to highly optimized C/C++/Fortran backends. You write Python for the 95% of your code that is glue logic, and the hot inner loop runs at native speed.
- The trade-off is developer velocity vs. execution speed. Instagram runs one of the largest Django deployments in the world. They chose Python because shipping features fast was more valuable than saving a few milliseconds per request. When they hit real performance walls, they optimized the hot paths with Cython or moved specific services to C++, but the majority of the codebase stayed Python.
- There are also runtime alternatives: PyPy uses JIT compilation and can be 5-10x faster than CPython for long-running processes. Python 3.13+ introduced experimental free-threaded builds and the Faster CPython initiative has been delivering incremental speedups each release.
- The key insight is to separate the orchestration layer from the compute layer. Python excels at orchestration — reading configuration, managing DAGs, coordinating services, handling retries. The actual heavy lifting gets delegated to optimized engines.
- Concretely, I would use something like Apache Kafka or AWS Kinesis for ingestion, with Python consumers using the confluent-kafka library (which is a C wrapper). For transformation, Polars or DuckDB for in-process analytics — both are Rust/C++ engines with Python bindings that operate on columnar data orders of magnitude faster than pure Python loops.
- For parallelism, I would use multiprocessing or distribute across workers with Celery or Ray. Each worker process gets its own GIL, so you get true CPU parallelism. The coordination, monitoring, and error handling — the complex part that changes frequently — stays in readable Python.
- The anti-pattern is writing a for-loop over 50 million rows in pure Python. That is not “Python is slow” — that is using the wrong tool for the job, like using a screwdriver to hammer a nail.
What does 'Pythonic' actually mean in practice, and can you give an example where the Pythonic approach is genuinely better, not just shorter?
What does 'Pythonic' actually mean in practice, and can you give an example where the Pythonic approach is genuinely better, not just shorter?
Strong Answer:
- Pythonic code leverages the language’s idioms, protocols, and built-in abstractions to produce code that is not just concise but more correct and more performant. It is not about being clever or writing one-liners — it is about working with the language instead of against it.
- A concrete example: iterating over a collection while needing the index. The non-Pythonic way is to use
range(len(items))and index into the list. The Pythonic way isenumerate(items). This is not just shorter — it eliminates an entire class of off-by-one errors, it is faster because Python’s iterator protocol avoids repeated__getitem__calls, and it communicates intent (“I need both index and value”) immediately. - Another example with real impact: checking if a key exists in a dictionary before accessing it. Non-Pythonic code does
if key in d: value = d[key]— two hash lookups. Pythonic code usesvalue = d.get(key, default)— one hash lookup, half the work, and impossible to hit a race condition in concurrent code where the key might be deleted between the check and the access. - The deeper principle is EAFP (Easier to Ask Forgiveness than Permission) vs. LBYL (Look Before You Leap). Pythonic code prefers
try/exceptover precondition checks when the common case is success. This is not just style — it is actually faster in CPython because the try block has near-zero overhead when no exception is raised, whereas precondition checks execute every time. - The most meaningful example I have seen: using context managers (
withstatements) instead of manual try/finally for resource cleanup. The Pythonic version is not just shorter — it makes resource leaks structurally impossible. You cannot forget to close the file because the protocol handles it.
- Absolutely. EAFP breaks down when the “exceptional” case is actually common. If you are doing a dictionary lookup and expect the key to be missing 50% of the time, using
try/except KeyErroris significantly slower thanif key in dbecause exception handling in CPython is expensive — it has to unwind the call stack, create a traceback object, and allocate an exception instance. For hot loops where misses are frequent, LBYL wins on performance. - Another case: list comprehensions can become unreadable when they involve nested loops with conditions. A three-level nested comprehension with filtering is technically “Pythonic” but violates the Zen of Python’s “readability counts.” A regular for-loop with clear variable names is better there.
- The general principle is that Pythonic is a heuristic, not a law. The Zen itself says “practicality beats purity.”
Python uses reference counting plus a cyclic garbage collector. Walk me through how memory management works and where it can bite you in production.
Python uses reference counting plus a cyclic garbage collector. Walk me through how memory management works and where it can bite you in production.
Strong Answer:
- Python’s primary garbage collection mechanism is reference counting. Every object has a counter tracking how many references point to it. When you do
x = [1, 2, 3], that list’s refcount is 1. If you doy = x, refcount goes to 2. Whenxgoes out of scope or is reassigned, refcount drops to 1. When it hits 0, the memory is freed immediately — no waiting for a GC cycle. - The problem with pure reference counting is circular references. If object A references object B and object B references object A, both refcounts stay at 1 even when nothing else points to them. They are unreachable but never freed. This is why CPython has a secondary cyclic garbage collector that periodically scans for and breaks these reference cycles.
- The cyclic GC uses a generational approach with three generations (0, 1, 2). New objects start in generation 0. If they survive a collection, they get promoted to generation 1, then generation 2. Higher generations are collected less frequently, based on the assumption that long-lived objects are less likely to become garbage. This is the same heuristic the JVM uses.
- Where this bites you in production: the cyclic GC can introduce latency spikes. When it runs, it pauses the application to scan objects. Instagram famously disabled the cyclic garbage collector in their Django processes and saw a 10% memory improvement. They could do this because their code was carefully written to avoid circular references, so the cyclic GC was doing work for nothing.
- Another production gotcha:
__del__finalizers. If an object with a__del__method is part of a reference cycle, CPython cannot safely collect it (before Python 3.4) because it does not know in what order to call the finalizers. These objects end up ingc.garbageand leak forever. Python 3.4+ improved this with PEP 442, but relying on__del__for cleanup is still an anti-pattern — use context managers andwithstatements instead. - Memory fragmentation is another real concern. CPython’s memory allocator (pymalloc) uses arenas of 256KB. If even one object in an arena is still alive, the entire arena cannot be returned to the OS. Long-running processes that create and destroy many objects of varying sizes can see their resident memory grow monotonically even though logical usage is flat. This is why long-running Python workers are often restarted periodically (Gunicorn’s
max_requestssetting exists precisely for this reason).
- First, I would distinguish between a true leak (unreachable objects never freed) and memory bloat (reachable objects that should have been released but are still referenced). In practice, the second case is far more common in Python.
- I would start with
tracemalloc, which is built into the standard library. You enable it at startup withtracemalloc.start(), take snapshots at intervals, and compare them to see which lines of code are allocating the most memory over time. This gives you file, line number, and allocation size. - For finding reference cycles,
gc.set_debug(gc.DEBUG_SAVEALL)will save unreachable objects togc.garbageso you can inspect them. Theobjgraphlibrary is excellent for visualizing reference chains —objgraph.show_backrefs(obj)will draw a graph showing what is keeping an object alive. - In production, I would use
memory_profileror attachpyspyfor sampling-based profiling with minimal overhead. If it is a web service, adding a/debug/memoryendpoint that returnstracemalloctop stats is invaluable for diagnosing issues without redeploying.