Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save carefree-ladka/f3dfebaca57c1a88eb86e9d6e936cf17 to your computer and use it in GitHub Desktop.

Select an option

Save carefree-ladka/f3dfebaca57c1a88eb86e9d6e936cf17 to your computer and use it in GitHub Desktop.
🐍 The Complete Python Learning Guide

🐍 The Complete Python Learning Guide

A comprehensive, beginner-to-advanced reference for learning Python β€” from your first print("Hello, World!") to building real-world applications.


Table of Contents

  1. Introduction to Python

  2. Python Basics

  3. Operators

  4. Control Flow

  5. Functions

  6. Data Structures

  7. Strings

  8. Object-Oriented Programming

  9. Modules and Packages

  10. File Handling

  11. Error Handling

  12. Iterators and Generators

  13. Concurrency

  14. Popular Libraries

  15. Best Practices and Pythonic Code

  16. Learning Roadmap


1. Introduction to Python

What is Python?

Python is a high-level, interpreted, general-purpose programming language created by Guido van Rossum and first released in 1991. Its design philosophy emphasizes code readability with the use of significant indentation.

Python is:

  • Interpreted β€” runs line by line, no compilation needed
  • Dynamically typed β€” variable types are determined at runtime
  • Multi-paradigm β€” supports procedural, OOP, and functional styles
  • Batteries included β€” a rich standard library ships with it

Why Learn Python?

Use Case Examples
Web Development Django, Flask, FastAPI
Data Science NumPy, Pandas, SciPy
Machine Learning / AI TensorFlow, PyTorch, scikit-learn
Automation & Scripting Selenium, PyAutoGUI
DevOps / Cloud Ansible, Boto3 (AWS)
Game Development Pygame
Cybersecurity Scapy, pwntools

Python consistently ranks #1 or #2 on language popularity indices (TIOBE, Stack Overflow surveys) and is the most recommended first language for beginners.

Installing Python

Windows / macOS / Linux:

  1. Go to https://www.python.org/downloads/
  2. Download the latest stable release (Python 3.12+)
  3. During installation on Windows, βœ… check "Add Python to PATH"

Verify installation:

python --version
# Python 3.12.x

pip --version
# pip 24.x

πŸ’‘ Tip: Use pyenv to manage multiple Python versions on the same machine.

Your First Python Program

# hello.py
print("Hello, World!")

Run it:

python hello.py
# Hello, World!

2. Python Basics

Variables and Data Types

Python has no explicit type declaration. Variables are created on assignment.

# Integer
age = 25

# Float
price = 19.99

# String
name = "Alice"

# Boolean
is_active = True

# NoneType
result = None

# Check type
print(type(age))      # <class 'int'>
print(type(price))    # <class 'float'>
print(type(name))     # <class 'str'>
print(type(is_active))# <class 'bool'>

Multiple assignment:

x = y = z = 0          # All three point to 0
a, b, c = 1, 2, 3      # Unpacking
first, *rest = [1, 2, 3, 4]  # Star unpacking β†’ first=1, rest=[2,3,4]

Type Conversion

# Implicit
result = 5 + 2.0        # β†’ 7.0 (int promoted to float)

# Explicit (casting)
int("42")               # β†’ 42
float("3.14")           # β†’ 3.14
str(100)                # β†’ "100"
bool(0)                 # β†’ False
bool("hello")           # β†’ True
list("abc")             # β†’ ['a', 'b', 'c']

Input and Output

# Output
print("Hello")                          # Hello
print("a", "b", "c", sep="-")          # a-b-c
print("no newline", end="")             # prints without newline

# Input (always returns a string)
name = input("Enter your name: ")
age = int(input("Enter your age: "))    # cast to int

Comments

# Single-line comment

"""
Multi-line comment (technically a string literal,
but commonly used as a block comment or docstring)
"""

def greet(name):
    """
    Docstring: describes the function.
    Args:
        name (str): the person's name
    Returns:
        str: greeting message
    """
    return f"Hello, {name}!"

3. Operators

Arithmetic Operators

a, b = 10, 3

a + b    # 13  β†’ Addition
a - b    # 7   β†’ Subtraction
a * b    # 30  β†’ Multiplication
a / b    # 3.333... β†’ Division (always float)
a // b   # 3   β†’ Floor division
a % b    # 1   β†’ Modulo (remainder)
a ** b   # 1000 β†’ Exponentiation

Comparison Operators

5 == 5   # True   β†’ Equal
5 != 4   # True   β†’ Not equal
5 > 3    # True   β†’ Greater than
5 < 3    # False  β†’ Less than
5 >= 5   # True   β†’ Greater than or equal
5 <= 4   # False  β†’ Less than or equal

Logical Operators

True and False   # False
True or False    # True
not True         # False

# Short-circuit evaluation
x = None
y = x or "default"   # y = "default"  (x is falsy)

Assignment Operators

x = 10
x += 5    # x = 15
x -= 3    # x = 12
x *= 2    # x = 24
x //= 5   # x = 4
x **= 2   # x = 16
x %= 5    # x = 1

Bitwise Operators

a = 0b1010   # 10
b = 0b1100   # 12

a & b    # 0b1000 β†’ 8  (AND)
a | b    # 0b1110 β†’ 14 (OR)
a ^ b    # 0b0110 β†’ 6  (XOR)
~a       # -11          (NOT)
a << 1   # 0b10100β†’ 20 (Left shift)
a >> 1   # 0b0101 β†’ 5  (Right shift)

4. Control Flow

if / elif / else

score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(f"Grade: {grade}")  # Grade: B

Ternary (one-line if):

status = "adult" if age >= 18 else "minor"

for Loops

# Iterate over a list
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

# Range
for i in range(5):       # 0, 1, 2, 3, 4
    print(i)

for i in range(2, 10, 2): # 2, 4, 6, 8
    print(i)

# Enumerate (index + value)
for i, fruit in enumerate(fruits, start=1):
    print(f"{i}. {fruit}")

# Zip (parallel iteration)
names = ["Alice", "Bob"]
scores = [95, 87]
for name, score in zip(names, scores):
    print(f"{name}: {score}")

while Loops

count = 0
while count < 5:
    print(count)
    count += 1

# Infinite loop with break
while True:
    user_input = input("Type 'quit' to exit: ")
    if user_input == "quit":
        break

break, continue, pass

for i in range(10):
    if i == 3:
        continue   # Skip 3
    if i == 7:
        break      # Stop at 7
    print(i)       # Prints: 0 1 2 4 5 6

# pass β€” a no-op placeholder
def future_function():
    pass  # TODO: implement later

List Comprehensions

# Basic
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

# With condition
evens = [x for x in range(20) if x % 2 == 0]

# Nested
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]

# Dictionary comprehension
word_lengths = {word: len(word) for word in ["python", "is", "fun"]}
# {'python': 6, 'is': 2, 'fun': 3}

# Set comprehension
unique_chars = {c for c in "hello world" if c != " "}

# Generator expression (memory-efficient)
total = sum(x**2 for x in range(1000))

5. Functions

Defining Functions

def add(a, b):
    """Returns the sum of a and b."""
    return a + b

result = add(3, 4)   # 7

Arguments and Parameters

# Default arguments
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

greet("Alice")           # "Hello, Alice!"
greet("Bob", "Hi")       # "Hi, Bob!"

# Keyword arguments
greet(greeting="Hey", name="Carol")

# *args β€” variable positional arguments
def total(*args):
    return sum(args)

total(1, 2, 3, 4)   # 10

# **kwargs β€” variable keyword arguments
def describe(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

describe(name="Alice", age=30, city="Delhi")

# Positional-only (/) and keyword-only (*) parameters
def strict(pos_only, /, normal, *, kw_only):
    pass

Lambda Functions

# Anonymous, single-expression functions
square = lambda x: x ** 2
square(5)   # 25

# Common use: sorting
people = [{"name": "Bob", "age": 30}, {"name": "Alice", "age": 25}]
people.sort(key=lambda p: p["age"])

# With map, filter, reduce
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))
evens = list(filter(lambda x: x % 2 == 0, nums))

Recursion

def factorial(n):
    if n <= 1:       # Base case
        return 1
    return n * factorial(n - 1)  # Recursive case

factorial(5)   # 120

# Fibonacci with memoization
from functools import lru_cache

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

Decorators

import time

def timer(func):
    """Measures execution time of a function."""
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} took {end - start:.4f}s")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(1)
    return "done"

slow_function()   # slow_function took 1.0001s

# Stacking decorators
@timer
@lru_cache(maxsize=128)
def expensive_calc(n):
    return sum(range(n))

6. Data Structures

Lists

# Creation
fruits = ["apple", "banana", "cherry"]
empty = []
mixed = [1, "two", 3.0, True]

# Indexing & Slicing
fruits[0]       # "apple"
fruits[-1]      # "cherry"
fruits[1:3]     # ["banana", "cherry"]
fruits[::-1]    # Reversed: ["cherry", "banana", "apple"]

# Common methods
fruits.append("date")        # Add to end
fruits.insert(1, "avocado")  # Insert at index
fruits.remove("banana")      # Remove first occurrence
popped = fruits.pop()        # Remove & return last
fruits.sort()                # In-place sort
fruits.sort(reverse=True)    # Descending
sorted_copy = sorted(fruits) # Returns new sorted list
fruits.reverse()             # Reverse in-place
fruits.index("apple")        # Find index
fruits.count("apple")        # Count occurrences
fruits.extend(["elderberry"]) # Merge lists
len(fruits)                  # Length
"apple" in fruits            # Membership test

Tuples

# Immutable sequences
point = (3, 4)
single = (42,)         # Note the trailing comma!
packed = 1, 2, 3       # Parentheses optional

# Unpacking
x, y = point
a, b, *rest = (1, 2, 3, 4, 5)   # a=1, b=2, rest=[3,4,5]

# Named tuples (readable records)
from collections import namedtuple
Person = namedtuple("Person", ["name", "age", "city"])
alice = Person("Alice", 30, "Delhi")
alice.name    # "Alice"

Sets

# Unordered collection of unique elements
fruits = {"apple", "banana", "cherry", "apple"}  # {"apple","banana","cherry"}
empty_set = set()   # {} creates a dict, not a set!

# Operations
fruits.add("date")
fruits.discard("banana")    # No error if missing
fruits.remove("cherry")     # Raises KeyError if missing

# Set math
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}
a | b     # Union: {1,2,3,4,5,6}
a & b     # Intersection: {3,4}
a - b     # Difference: {1,2}
a ^ b     # Symmetric difference: {1,2,5,6}
a <= b    # Subset check

Dictionaries

# Key-value pairs (ordered since Python 3.7+)
person = {"name": "Alice", "age": 30, "city": "Delhi"}

# Access
person["name"]              # "Alice"
person.get("phone", "N/A")  # "N/A" (safe, no KeyError)

# Modify
person["age"] = 31
person["email"] = "alice@example.com"
del person["city"]

# Iteration
for key in person:               # Keys
for value in person.values():    # Values
for key, value in person.items():# Key-value pairs

# Useful methods
person.keys()
person.values()
person.items()
person.update({"age": 32, "phone": "9876543210"})
person.pop("email", None)         # Remove & return; None if missing
person.setdefault("city", "Mumbai")  # Set only if key absent

# Dictionary comprehension
squares = {n: n**2 for n in range(1, 6)}

# Merging (Python 3.9+)
merged = dict1 | dict2

Stacks and Queues

# Stack (LIFO) β€” use a list
stack = []
stack.append(1)      # push
stack.append(2)
stack.pop()          # pop β†’ 2

# Queue (FIFO) β€” use deque for O(1) pops from left
from collections import deque
queue = deque()
queue.append("first")
queue.append("second")
queue.popleft()      # β†’ "first"

# Priority Queue
import heapq
pq = []
heapq.heappush(pq, (2, "low priority"))
heapq.heappush(pq, (1, "high priority"))
heapq.heappop(pq)    # β†’ (1, "high priority")

7. Strings

String Methods

s = "  Hello, Python World!  "

s.strip()             # Remove whitespace: "Hello, Python World!"
s.lstrip() / s.rstrip()
s.lower()             # "  hello, python world!  "
s.upper()             # "  HELLO, PYTHON WORLD!  "
s.title()             # "  Hello, Python World!  "
s.replace("Python", "Amazing")
s.split(", ")         # ["  Hello", "Python World!  "]
s.split()             # Split on whitespace (strips too)
",".join(["a","b","c"])  # "a,b,c"
s.startswith("  He")  # True
s.endswith("!  ")     # True
s.find("Python")      # 9 (index) or -1 if not found
s.count("l")          # 3
s.isdigit()           # False
s.isalpha()           # False
s.isalnum()           # False
s.center(30, "*")     # Center with padding

String Formatting

name, age = "Alice", 30

# f-strings (Python 3.6+) β€” preferred
f"Name: {name}, Age: {age}"
f"Pi is approximately {3.14159:.2f}"
f"{'title':^20}"          # Centered in 20 chars
f"{1000000:,}"            # "1,000,000"

# .format()
"Hello, {}! You are {} years old.".format(name, age)
"{name} is {age}".format(name="Bob", age=25)

# % formatting (legacy)
"Hello, %s! Age: %d" % (name, age)

# Multi-line f-string
message = (
    f"Name: {name}\n"
    f"Age:  {age}\n"
)

Regular Expressions

import re

text = "My email is alice@example.com and phone is +91-9876543210"

# Search
match = re.search(r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b', text)
if match:
    print(match.group())   # alice@example.com

# Find all
re.findall(r'\d+', text)      # ['91', '9876543210']

# Replace
clean = re.sub(r'\s+', ' ', "too   many    spaces")

# Compile for repeated use
email_pattern = re.compile(r'[\w.+-]+@[\w-]+\.[\w.]+')
emails = email_pattern.findall(text)

# Groups
date_match = re.search(r'(\d{4})-(\d{2})-(\d{2})', "Born: 1994-07-15")
year, month, day = date_match.groups()

8. Object-Oriented Programming

Classes and Objects

class Dog:
    # Class variable (shared by all instances)
    species = "Canis lupus familiaris"

    def __init__(self, name, age):   # Constructor
        self.name = name             # Instance variable
        self.age = age

    def bark(self):
        return f"{self.name} says: Woof!"

    def __str__(self):               # String representation
        return f"Dog(name={self.name}, age={self.age})"

    def __repr__(self):
        return f"Dog({self.name!r}, {self.age!r})"

    @classmethod
    def from_birth_year(cls, name, birth_year):
        return cls(name, 2024 - birth_year)

    @staticmethod
    def is_adult(age):
        return age >= 2

# Usage
rex = Dog("Rex", 3)
print(rex.bark())         # Rex says: Woof!
print(rex)                # Dog(name=Rex, age=3)
print(Dog.species)        # Canis lupus familiaris
buddy = Dog.from_birth_year("Buddy", 2020)
Dog.is_adult(3)           # True

Inheritance

class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        raise NotImplementedError("Subclasses must implement speak()")

    def __str__(self):
        return f"{type(self).__name__}({self.name})"


class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"


class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"


class GuideDog(Dog):
    """Multiple levels of inheritance."""
    def __init__(self, name, owner):
        super().__init__(name)       # Call parent __init__
        self.owner = owner

    def speak(self):
        base = super().speak()
        return f"{base} (Guide dog for {self.owner})"


# Polymorphism
animals = [Dog("Rex"), Cat("Whiskers"), GuideDog("Buddy", "Alice")]
for animal in animals:
    print(animal.speak())

# isinstance / issubclass
isinstance(rex, Dog)      # True
isinstance(rex, Animal)   # True
issubclass(Dog, Animal)   # True

Encapsulation and Polymorphism

class BankAccount:
    def __init__(self, owner, balance=0):
        self.owner = owner
        self._balance = balance         # "protected" (convention)
        self.__pin = "1234"             # "private" (name-mangled)

    @property
    def balance(self):
        """Getter."""
        return self._balance

    @balance.setter
    def balance(self, amount):
        """Setter with validation."""
        if amount < 0:
            raise ValueError("Balance cannot be negative")
        self._balance = amount

    def deposit(self, amount):
        if amount > 0:
            self._balance += amount

    def withdraw(self, amount):
        if amount > self._balance:
            raise ValueError("Insufficient funds")
        self._balance -= amount

account = BankAccount("Alice", 1000)
account.deposit(500)
print(account.balance)      # 1500
account.balance = 2000      # Uses setter

Magic Methods (Dunder)

class Vector:
    def __init__(self, x, y):
        self.x, self.y = x, y

    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)

    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __len__(self):
        return 2

    def __getitem__(self, index):
        return (self.x, self.y)[index]

    def __iter__(self):
        yield self.x
        yield self.y

    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

    def __abs__(self):
        return (self.x**2 + self.y**2) ** 0.5


v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)      # Vector(4, 6)
print(abs(v2))      # 5.0
print(list(v1))     # [1, 2]

9. Modules and Packages

Importing Modules

import math                          # Import whole module
from math import sqrt, pi            # Import specific names
from math import sqrt as sq          # Alias
import numpy as np                   # Common alias convention

math.sqrt(16)   # 4.0
sqrt(25)        # 5.0
sq(9)           # 3.0

Creating Your Own Module

# myutils.py
def add(a, b):
    return a + b

def multiply(a, b):
    return a * b

CONSTANT = 42

if __name__ == "__main__":
    # Code here only runs when executed directly, not on import
    print("Running myutils directly")
# main.py
import myutils
from myutils import add, CONSTANT

result = myutils.multiply(3, 4)
print(add(2, 3))         # 5
print(CONSTANT)          # 42

The Python Standard Library

Module Purpose
os OS interaction, file paths
sys Python interpreter settings
pathlib Object-oriented file paths
datetime Dates and times
collections deque, Counter, defaultdict
itertools Efficient iteration tools
functools Higher-order functions, lru_cache
json JSON encoding/decoding
re Regular expressions
math Math functions
random Random number generation
time Time access and conversions
threading Thread-based parallelism
subprocess Run external commands
logging Logging infrastructure
unittest Unit testing framework
from collections import Counter, defaultdict
from itertools import chain, combinations, product
import datetime

# Counter
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
count = Counter(words)
count.most_common(2)    # [('apple', 3), ('banana', 2)]

# defaultdict
dd = defaultdict(list)
dd["fruits"].append("apple")  # No KeyError

# datetime
now = datetime.datetime.now()
today = datetime.date.today()
delta = datetime.timedelta(days=7)
next_week = today + delta

pip and Virtual Environments

# Install a package
pip install requests

# Install specific version
pip install requests==2.31.0

# Install from requirements file
pip install -r requirements.txt

# List installed packages
pip list
pip freeze > requirements.txt

# Create a virtual environment
python -m venv venv

# Activate
# Windows:
venv\Scripts\activate
# macOS/Linux:
source venv/bin/activate

# Deactivate
deactivate

πŸ’‘ Best Practice: Always use a virtual environment per project to avoid dependency conflicts.


10. File Handling

Reading and Writing Files

# Writing a file
with open("output.txt", "w", encoding="utf-8") as f:
    f.write("Line 1\n")
    f.writelines(["Line 2\n", "Line 3\n"])

# Reading a file
with open("output.txt", "r", encoding="utf-8") as f:
    content = f.read()          # Entire file as string

with open("output.txt", "r") as f:
    lines = f.readlines()       # List of lines

with open("output.txt", "r") as f:
    for line in f:              # Memory-efficient iteration
        print(line.strip())

# Append
with open("output.txt", "a") as f:
    f.write("Line 4\n")

# File modes: 'r' read | 'w' write | 'a' append | 'b' binary | '+' update

Working with CSV and JSON

import csv
import json

# CSV write
with open("data.csv", "w", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["name", "age"])
    writer.writeheader()
    writer.writerows([{"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}])

# CSV read
with open("data.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["age"])

# JSON write
data = {"name": "Alice", "scores": [95, 87, 92]}
with open("data.json", "w") as f:
    json.dump(data, f, indent=2)

# JSON read
with open("data.json") as f:
    loaded = json.load(f)

# String conversions
json_str = json.dumps(data)          # dict β†’ JSON string
parsed = json.loads('{"x": 1}')     # JSON string β†’ dict

Pathlib

from pathlib import Path

# Create path objects
home = Path.home()
project = Path("my_project")
config = project / "config" / "settings.json"

# Operations
config.parent           # Path("my_project/config")
config.name             # "settings.json"
config.stem             # "settings"
config.suffix           # ".json"

# Filesystem
config.exists()
config.is_file()
config.is_dir()
config.parent.mkdir(parents=True, exist_ok=True)
config.write_text('{"debug": true}')
content = config.read_text()

# Glob
for py_file in project.rglob("*.py"):
    print(py_file)

11. Error Handling

try / except / finally

def safe_divide(a, b):
    try:
        result = a / b
    except ZeroDivisionError:
        print("Error: Cannot divide by zero")
        return None
    except TypeError as e:
        print(f"Type error: {e}")
        return None
    else:
        print("Division succeeded!")    # Runs only if no exception
        return result
    finally:
        print("This always runs")       # Cleanup

safe_divide(10, 2)    # 5.0 + messages
safe_divide(10, 0)    # None + messages

# Catching multiple exceptions
try:
    risky_operation()
except (ValueError, KeyError) as e:
    print(f"Caught: {e}")

# Catching any exception
try:
    risky_operation()
except Exception as e:
    print(f"Unexpected error: {type(e).__name__}: {e}")
    raise    # Re-raise the exception

Raising Exceptions

def set_age(age):
    if not isinstance(age, int):
        raise TypeError(f"Age must be int, got {type(age).__name__}")
    if age < 0 or age > 150:
        raise ValueError(f"Age {age} is out of valid range (0–150)")
    return age

# Exception chaining
try:
    int("not a number")
except ValueError as e:
    raise RuntimeError("Failed to parse config") from e

Custom Exceptions

class AppError(Exception):
    """Base exception for this application."""
    pass

class ValidationError(AppError):
    def __init__(self, field, message):
        self.field = field
        self.message = message
        super().__init__(f"Validation failed on '{field}': {message}")

class DatabaseError(AppError):
    pass

# Usage
try:
    raise ValidationError("email", "Invalid format")
except ValidationError as e:
    print(e.field)      # email
    print(e.message)    # Invalid format
    print(e)            # Validation failed on 'email': Invalid format

12. Iterators and Generators

Iterators

# Any object with __iter__ and __next__
class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

for num in Countdown(5):
    print(num)   # 5 4 3 2 1

# Built-in iterators
it = iter([1, 2, 3])
next(it)    # 1
next(it)    # 2

Generators

def fibonacci():
    """Infinite Fibonacci generator."""
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

gen = fibonacci()
[next(gen) for _ in range(10)]
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

def read_large_file(filepath):
    """Memory-efficient file reader."""
    with open(filepath) as f:
        for line in f:
            yield line.strip()

# Generator pipeline
def integers_from(n):
    while True:
        yield n
        n += 1

def take(n, iterable):
    for i, item in enumerate(iterable):
        if i >= n:
            break
        yield item

list(take(5, integers_from(10)))    # [10, 11, 12, 13, 14]

yield Keyword

def batch_processor(data, batch_size=3):
    """Yields data in chunks."""
    batch = []
    for item in data:
        batch.append(item)
        if len(batch) == batch_size:
            yield batch
            batch = []
    if batch:
        yield batch

for batch in batch_processor(range(10), 3):
    print(batch)
# [0, 1, 2]
# [3, 4, 5]
# [6, 7, 8]
# [9]

# yield from β€” delegate to sub-generator
def chain(*iterables):
    for it in iterables:
        yield from it

list(chain([1, 2], [3, 4], [5]))   # [1, 2, 3, 4, 5]

13. Concurrency

Threading

import threading
import time

def download(url):
    print(f"Downloading {url}...")
    time.sleep(2)               # Simulate network I/O
    print(f"Done: {url}")

urls = ["url1.com", "url2.com", "url3.com"]
threads = [threading.Thread(target=download, args=(url,)) for url in urls]

for t in threads:
    t.start()

for t in threads:
    t.join()     # Wait for all to finish

print("All downloads complete")

# Thread-safe data with Lock
counter = 0
lock = threading.Lock()

def increment():
    global counter
    with lock:
        counter += 1

Multiprocessing

from multiprocessing import Pool
import os

def cpu_bound_task(n):
    return sum(i * i for i in range(n))

# Use all CPU cores
with Pool(processes=os.cpu_count()) as pool:
    results = pool.map(cpu_bound_task, [10**6, 10**6, 10**6, 10**6])

print(results)

πŸ’‘ Rule of thumb: Use threading for I/O-bound tasks; multiprocessing for CPU-bound tasks. The Python GIL limits true thread parallelism for CPU work.

Async / Await (asyncio)

import asyncio
import aiohttp

async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()

async def main():
    urls = [
        "https://api.github.com",
        "https://httpbin.org/get",
    ]
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
    return results

asyncio.run(main())

# Simple async example
async def greet(name, delay):
    await asyncio.sleep(delay)
    print(f"Hello, {name}!")

async def run():
    await asyncio.gather(
        greet("Alice", 2),
        greet("Bob", 1),
        greet("Carol", 0.5),
    )

asyncio.run(run())
# Hello, Carol!  (after 0.5s)
# Hello, Bob!    (after 1s)
# Hello, Alice!  (after 2s)

14. Popular Libraries

NumPy

import numpy as np

# Arrays
a = np.array([1, 2, 3, 4, 5])
b = np.zeros((3, 3))
c = np.ones((2, 4))
d = np.arange(0, 10, 2)          # [0 2 4 6 8]
e = np.linspace(0, 1, 5)         # [0.  0.25 0.5  0.75 1. ]

# Operations (vectorized, very fast)
a * 2                             # [2 4 6 8 10]
a ** 2                            # [1 4 9 16 25]
np.sqrt(a)

# 2D arrays
matrix = np.array([[1, 2], [3, 4]])
matrix.T                         # Transpose
np.dot(matrix, matrix)           # Matrix multiply (or @)
matrix @ matrix
matrix.shape                     # (2, 2)
matrix.reshape(1, 4)             # [[1 2 3 4]]
np.mean(matrix, axis=0)          # Column means

Pandas

import pandas as pd

# DataFrame creation
df = pd.DataFrame({
    "name": ["Alice", "Bob", "Carol"],
    "age": [25, 30, 35],
    "score": [88, 92, 79]
})

# Basic exploration
df.head(2)
df.info()
df.describe()
df.shape            # (3, 3)

# Selecting
df["name"]                           # Series
df[["name", "score"]]                # DataFrame
df[df["age"] > 25]                   # Filter
df.loc[0, "name"]                    # Label-based
df.iloc[0, 0]                        # Position-based

# Operations
df["score_pct"] = df["score"] / 100  # New column
df.sort_values("age", ascending=False)
df.groupby("age").mean(numeric_only=True)
df.dropna()                          # Remove NaN rows
df.fillna(0)

# I/O
df.to_csv("data.csv", index=False)
df = pd.read_csv("data.csv")
df.to_excel("data.xlsx", index=False)

Matplotlib

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 2 * np.pi, 100)

fig, axes = plt.subplots(1, 2, figsize=(12, 4))

# Line plot
axes[0].plot(x, np.sin(x), label="sin(x)", color="blue")
axes[0].plot(x, np.cos(x), label="cos(x)", color="red", linestyle="--")
axes[0].set_title("Trigonometric Functions")
axes[0].legend()
axes[0].grid(True)

# Bar chart
categories = ["A", "B", "C", "D"]
values = [23, 45, 12, 67]
axes[1].bar(categories, values, color="steelblue")
axes[1].set_title("Category Comparison")
axes[1].set_ylabel("Value")

plt.tight_layout()
plt.savefig("plot.png", dpi=150)
plt.show()

Requests

import requests

# GET request
response = requests.get("https://api.github.com/users/python")
response.status_code        # 200
response.json()             # Parsed JSON
response.headers            # Response headers
response.raise_for_status() # Raises on 4xx/5xx

# POST with JSON
payload = {"username": "alice", "password": "secret"}
response = requests.post(
    "https://api.example.com/login",
    json=payload,
    headers={"Accept": "application/json"},
    timeout=10
)

# Session (reuse connection, persist cookies)
with requests.Session() as session:
    session.headers.update({"Authorization": "Bearer TOKEN"})
    r = session.get("https://api.example.com/data")

Flask (Web)

from flask import Flask, request, jsonify

app = Flask(__name__)

# In-memory "database"
todos = [
    {"id": 1, "task": "Learn Python", "done": False},
]

@app.route("/todos", methods=["GET"])
def get_todos():
    return jsonify(todos)

@app.route("/todos", methods=["POST"])
def add_todo():
    data = request.get_json()
    new_todo = {"id": len(todos) + 1, "task": data["task"], "done": False}
    todos.append(new_todo)
    return jsonify(new_todo), 201

@app.route("/todos/<int:todo_id>", methods=["DELETE"])
def delete_todo(todo_id):
    global todos
    todos = [t for t in todos if t["id"] != todo_id]
    return jsonify({"message": "Deleted"}), 200

if __name__ == "__main__":
    app.run(debug=True, port=5000)

15. Best Practices and Pythonic Code

PEP 8 Style Guide

# βœ… Good: snake_case for variables and functions
user_name = "Alice"
def calculate_total(items):
    pass

# βœ… Good: PascalCase for classes
class UserAccount:
    pass

# βœ… Good: UPPER_SNAKE_CASE for constants
MAX_RETRIES = 3
DEFAULT_TIMEOUT = 30

# βœ… Good: 4-space indentation
def is_even(n):
    return n % 2 == 0

# βœ… Good: blank lines (2 around top-level, 1 inside)
class Foo:
    def method_a(self):
        pass

    def method_b(self):
        pass

# βœ… Good: line length ≀ 79 chars; use implicit continuation
result = (
    some_long_variable_name
    + another_long_variable_name
    + yet_another_variable
)

Pythonic Idioms

# βœ… Use enumerate instead of range(len(...))
for i, item in enumerate(my_list):
    print(i, item)

# βœ… Swap without temp variable
a, b = b, a

# βœ… Use zip for parallel iteration
for name, score in zip(names, scores):
    print(f"{name}: {score}")

# βœ… Use any() / all()
any(x > 0 for x in numbers)
all(x > 0 for x in numbers)

# βœ… Use dict.get() with default
value = my_dict.get("key", "default")

# βœ… Use context managers
with open("file.txt") as f:
    data = f.read()

# βœ… EAFP over LBYL (ask for forgiveness, not permission)
try:
    value = my_dict["key"]
except KeyError:
    value = None

# βœ… Use dataclasses for simple data containers
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float
    label: str = "origin"
    tags: list = field(default_factory=list)

p = Point(3.0, 4.0, "A")
print(p)   # Point(x=3.0, y=4.0, label='A', tags=[])

Testing with pytest

# test_math_utils.py
import pytest

def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ZeroDivisionError
    return a / b

# Test functions must start with "test_"
def test_add():
    assert add(2, 3) == 5
    assert add(-1, 1) == 0
    assert add(0, 0) == 0

def test_divide():
    assert divide(10, 2) == 5.0

def test_divide_by_zero():
    with pytest.raises(ZeroDivisionError):
        divide(10, 0)

@pytest.mark.parametrize("a,b,expected", [
    (1, 2, 3),
    (0, 0, 0),
    (-1, 1, 0),
    (100, 200, 300),
])
def test_add_parametrized(a, b, expected):
    assert add(a, b) == expected
# Run tests
pytest
pytest -v                # Verbose
pytest -k "test_add"    # Filter by name
pytest --tb=short       # Short tracebacks

Debugging Tips

# 1. pdb β€” Python debugger
import pdb; pdb.set_trace()    # Breakpoint (legacy)
breakpoint()                    # Python 3.7+ shorthand

# pdb commands: n(ext), s(tep), c(ontinue), p(rint), q(uit), l(ist)

# 2. Print debugging (quick and dirty)
print(f"DEBUG: {variable = }")   # Python 3.8+ f-string = trick

# 3. logging (production-grade)
import logging

logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s %(levelname)s %(name)s: %(message)s"
)
logger = logging.getLogger(__name__)

logger.debug("Processing item %d", item_id)
logger.info("Request completed in %.2fs", elapsed)
logger.warning("Cache miss for key: %s", key)
logger.error("Failed to connect: %s", str(e))
logger.exception("Unhandled exception")   # Includes traceback

# 4. icecream (pip install icecream)
from icecream import ic
ic(my_variable)   # Pretty-prints with variable name

16. Learning Roadmap

BEGINNER (0–3 months)
β”œβ”€β”€ Python syntax & data types
β”œβ”€β”€ Control flow (if, for, while)
β”œβ”€β”€ Functions & modules
β”œβ”€β”€ File I/O
└── Basic OOP

INTERMEDIATE (3–6 months)
β”œβ”€β”€ Advanced OOP (inheritance, decorators)
β”œβ”€β”€ Error handling
β”œβ”€β”€ Comprehensions & generators
β”œβ”€β”€ Standard library (collections, itertools, datetime)
β”œβ”€β”€ Testing with pytest
β”œβ”€β”€ Virtual environments & pip
└── Working with APIs (requests)

ADVANCED (6–12 months)
β”œβ”€β”€ Async programming (asyncio)
β”œβ”€β”€ Concurrency (threading, multiprocessing)
β”œβ”€β”€ Type hints & mypy
β”œβ”€β”€ Design patterns
β”œβ”€β”€ Database access (SQLAlchemy)
└── Packaging your own library

SPECIALIZATION (choose your path)
β”œβ”€β”€ πŸ“Š Data Science β†’ NumPy, Pandas, Matplotlib, Jupyter
β”œβ”€β”€ πŸ€– ML / AI β†’ scikit-learn, TensorFlow, PyTorch
β”œβ”€β”€ 🌐 Web Dev β†’ Flask/FastAPI/Django
β”œβ”€β”€ βš™οΈ  DevOps β†’ Ansible, Boto3, Docker SDK
└── πŸ”’ Security β†’ Scapy, pwntools, cryptography

Recommended Resources

Resource Level Type
docs.python.org All Official Docs
Automate the Boring Stuff β€” Al Sweigart Beginner Book (free online)
Fluent Python β€” Luciano Ramalho Advanced Book
Python Tricks β€” Dan Bader Intermediate Book
Real Python (realpython.com) All Tutorials
LeetCode / HackerRank Intermediate+ Practice
PyPI (pypi.org) All Package Index

Happy coding! 🐍 The best way to learn Python is to build things you care about. Start small, be consistent, and the rest follows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment