Python: The Complete Reference
Syntax · Every Topic · All Common Errors · Real Usage
Covers exact syntax, common errors with fixes, and real-world use for every core Python topic -- from
variables to concurrency.
Table of Contents
1. Introduction
2. Python Syntax Rules
3. Variables and Memory Model
4. Data Types
5. Operators (Complete)
6. Strings -- Complete Reference
7. Conditionals -- Complete
8. Loops -- Complete
9. Functions -- Complete
10. Lists -- Complete
11. Tuples -- Complete
12. Dictionaries -- Complete
13. Sets -- Complete
14. Errors and Exceptions -- Complete Reference
15. Every Common Error, Explained
16. File Handling -- Complete
17. Modules and Packages -- Complete
18. Object-Oriented Programming -- Complete
19. Iterators and Generators -- Complete
20. Decorators -- Complete
21. Context Managers -- Complete
22. Functional Tools -- map, filter, reduce
23. Regular Expressions (re module)
24. The collections Module
25. Dates and Time
26. Testing and Debugging
27. Concurrency (Threading, Multiprocessing, Async) -- Overview
28. Key Libraries for Real Projects
29. Practice Roadmap
1. Introduction
This is a complete Python reference: every core topic, its exact syntax, common errors you'll hit while using
it, and where it's used in real code. Type every example yourself in Termux -- don't just read.
2. Python Syntax Rules
2.1 Indentation (not braces)
Python uses indentation (spaces) to define code blocks, not {} like C++/Java. This is the single most
common source of errors for beginners coming from other languages.
if True:
print("Indented 4 spaces -- this is a code block")
print("Same block, same indent level")
print("Back to column 0 -- outside the if block")
COMMON ERROR: IndentationError: expected an indented block
Cause: forgetting to indent after a colon (:), or mixing tabs and spaces.
Fix: use 4 spaces consistently, never mix tabs and spaces in the same file.
2.2 Comments
# This is a single-line comment
"""
This is a multi-line comment / docstring
used often at the top of files or functions
"""
x = 5 # inline comment explaining this line
2.3 Statements and line continuation
Normally one statement per line. A semicolon can join two statements on one line (rarely used, not
recommended). A backslash continues a statement onto the next line.
a = 1; b = 2 # works but considered bad style
total = 1 + 2 + 3 + \
4 + 5 + 6 # backslash continuation
total2 = (1 + 2 + 3 +
4 + 5 + 6) # preferred: parentheses continuation
2.4 Keywords (reserved words -- cannot be used as variable names)
False None True and as assert async await
break class continue def del elif else except
finally for from global if import in is
lambda nonlocal not or pass raise return try
while with yield
COMMON ERROR: SyntaxError: invalid syntax
Cause: using a keyword as a variable name, e.g. class = 5, or missing a colon.
Fix: rename the variable, or add the missing : after if/for/while/def/class.
2.5 Identifiers (naming rules)
Must start with a letter or underscore, can contain letters/numbers/underscores, case-sensitive, cannot be a
keyword.
valid_name = 1
_private = 2
name2 = 3
# 2name = 4 -- SyntaxError: invalid syntax (starts with digit)
# my-name = 5 -- SyntaxError (hyphen not allowed)
3. Variables and Memory Model
A variable is a name bound to an object in memory. Python is dynamically typed -- the same name can point
to different types over its lifetime.
name = "Tgr"
age = 20
age = "twenty" # legal in Python -- age now points to a string
x = 5
y = x # y points to the same value 5
print(id(x), id(y)) # id() shows memory address -- same for small ints (cached)
Multiple assignment and unpacking
x, y, z = 1, 2, 3
a = b = c = 0
first, *rest = [1, 2, 3, 4] # unpacking with *
print(first) # 1
print(rest) # [2, 3, 4]
x, y = y, x # swap without a temp variable
COMMON ERROR
NameError: name 'x' is not defined
Cause: using a variable before assigning it, or a typo in the name.
Fix: check spelling, and make sure the variable is assigned before use in that scope.
4. Data Types
4.1 Numeric types
a = 42 # int
b = 3.14 # float
c = 2 + 3j # complex
print(type(a), type(b), type(c))
# Number systems
binary = 0b1010 # binary, = 10
octal = 0o12 # octal, = 10
hexa = 0xA # hexadecimal, = 10
print(binary, octal, hexa)
print(bin(10)) # '0b1010'
print(hex(10)) # '0xa'
print(oct(10)) # '0o12'
4.2 Strings, Booleans, NoneType
s = "hello"
b = True
n = None # represents 'no value' -- used as a default/placeholder
print(type(s), type(b), type(n))
if n is None: # always compare None with 'is', not ==
print('n has no value yet')
4.3 Checking and converting types
x = "42"
print(type(x)) # <class 'str'>
print(isinstance(x, str)) # True -- preferred over type() == checks
y = int(x) # "42" -> 42
z = float(x) # "42" -> 42.0
w = str(100) # 100 -> "100"
b = bool(0) # False -- 0, 0.0, "", [], {}, None are all falsy
b2 = bool(5) # True -- any non-zero/non-empty value is truthy
COMMON ERROR: ValueError: invalid literal for int() with base 10: 'abc'
Cause: trying to convert a non-numeric string to int/float.
Fix: validate input first, or wrap the conversion in try/except.
COMMON ERROR: TypeError: unsupported operand type(s) for +: 'int' and 'str'
Cause: mixing incompatible types, e.g. 5 + "5".
Fix: convert explicitly: 5 + int("5") or str(5) + "5".
5. Operators (Complete)
5.1 Arithmetic
+ - * / // % **
print(10 / 3) # 3.333... true division
print(10 // 3) # 3 floor division
print(10 % 3) # 1 modulus/remainder
print(2 ** 10) # 1024 exponent
5.2 Comparison
== != > < >= <=
5.3 Logical
and or not
print(True and False) # False
print(True or False) # True
print(not True) # False
5.4 Bitwise
Used in: low-level flags, permissions systems, competitive programming optimizations.
& | ^ ~ << >>
print(5 & 3) # 1 AND
print(5 | 3) # 7 OR
print(5 ^ 3) # 6 XOR
print(~5) # -6 NOT
print(5 << 1) # 10 left shift (multiply by 2)
print(5 >> 1) # 2 right shift (divide by 2)
5.5 Identity operators (is / is not)
Checks if two names point to the exact same object in memory, not just equal value.
a = [1, 2]
b = [1, 2]
print(a == b) # True -- same value
print(a is b) # False -- different objects in memory
5.6 Membership operators (in / not in)
print(3 in [1, 2, 3]) # True
print("x" not in "abc") # True
5.7 Assignment operators
x = 5
x += 2 # x = x + 2
x -= 1
x *= 3
x /= 2
x //= 2
x **= 2
x %= 4
5.8 Walrus operator (:=) -- Python 3.8+
Assigns and returns a value in the same expression. Used to avoid calling a function twice, common in while
loops and comprehensions.
data = [1, 2, 3, 4, 5]
if (n := len(data)) > 3:
print(f"List has {n} items -- more than 3")
while (line := input("Enter text (or 'q'): ")) != "q":
print("You typed:", line)
5.9 Operator precedence (highest to lowest, key ones)
** exponent
+x, -x, ~x unary
* / // % multiplicative
+ - additive
<< >> shifts
& bitwise AND
^ bitwise XOR
| bitwise OR
== != < > <= >= is in comparisons
not logical NOT
and logical AND
or logical OR
6. Strings -- Complete Reference
6.1 Creating strings, escape sequences
s1 = "double quotes"
s2 = 'single quotes'
s3 = """triple quotes -- can span
multiple lines"""
# Escape sequences
print("Line1\nLine2") # \n = newline
print("Col1\tCol2") # \t = tab
print("She said \"hi\"") # \" = literal quote
print("C:\\path\\file") # \\ = literal backslash
raw = r"C:\path\file" # raw string -- ignores escape sequences
print(raw)
6.2 Indexing and slicing
s = "Hello World"
print(s[0]) # H -- first character
print(s[-1]) # d -- last character
print(s[0:5]) # Hello -- slice, index 0 up to (not including) 5
print(s[6:]) # World -- from index 6 to end
print(s[:5]) # Hello -- from start to index 5
print(s[::-1]) # dlroW olleH -- reversed, step -1
print(s[::2]) # Hlo ol -- every 2nd character
COMMON ERROR: IndexError: string index out of range
Cause: accessing an index that doesn't exist, e.g. s[50] on a 10-char string.
Fix: check len(s) first, or use slicing which never raises this error.
6.3 String methods (most used)
s = " Hello World "
print([Link]()) # removes leading/trailing whitespace
print([Link]()) # left strip only
print([Link]()) # right strip only
print([Link]()) # HELLO WORLD
print([Link]()) # hello world
print([Link]()) # Hello World (each word capitalized)
print([Link]().replace("World", "Python"))
print([Link]().split()) # ['Hello', 'World']
print("-".join(["a", "b", "c"])) # a-b-c
print([Link]().startswith("Hello")) # True
print([Link]().endswith("World")) # True
print([Link]().find("World")) # 6 (index) or -1 if not found
print([Link]().index("World")) # 6, but raises ValueError if not found
print([Link]("l")) # counts occurrences
print("42".isdigit()) # True
print("abc".isalpha()) # True
print("abc123".isalnum()) # True
6.4 String formatting -- 3 ways
name, age = "Tgr", 20
# 1. f-strings (modern, preferred, Python 3.6+)
print(f"{name} is {age} years old")
print(f"{age * 2}") # expressions work directly inside {}
pi = 3.14159
print(f"{pi:.2f}") # 3.14 -- 2 decimal places
# 2. .format() method
print("{} is {} years old".format(name, age))
print("{0} is {1}, again {0}".format(name, age)) # positional reuse
# 3. % formatting (old style, still seen in older codebases)
print("%s is %d years old" % (name, age))
COMMON ERROR: TypeError: can only concatenate str (not "int") to str
Cause: doing "Age: " + 20 directly.
Fix: use f-string f"Age: {20}" or str(20).
7. Conditionals -- Complete
marks = 78
if marks >= 90:
grade = "A"
elif marks >= 75:
grade = "B"
else:
grade = "F"
print(grade)
Ternary expression
status = "adult" if age >= 18 else "minor"
match-case (Python 3.10+, like switch in other languages)
day = 3
match day:
case 1:
print("Monday")
case 2:
print("Tuesday")
case 3 | 4: # matches 3 OR 4
print("Mid-week")
case _: # default case, like else
print("Other day")
COMMON ERROR: SyntaxError: expected ':'
Cause: forgetting the colon after if/elif/else/for/while/def/class.
Fix: every one of these statements must end with a colon before the indented block.
8. Loops -- Complete
8.1 for loop
for i in range(5): # 0 1 2 3 4
print(i)
for i in range(2, 10, 2): # start, stop, step -> 2 4 6 8
print(i)
for ch in "abc":
print(ch) # a b c
8.2 while loop
count = 0
while count < 5:
print(count)
count += 1
8.3 break, continue, pass, and the loop else clause
The else clause on a loop runs only if the loop completes WITHOUT hitting a break. Used for search
patterns -- 'else' means 'not found'.
numbers = [1, 3, 5, 7]
for n in numbers:
if n % 2 == 0:
print("Found an even number")
break
else:
print("No even number found") # this runs since no break happened
8.4 enumerate() -- get index + value together
fruits = ["apple", "banana", "cherry"]
for index, fruit in enumerate(fruits):
print(index, fruit)
for index, fruit in enumerate(fruits, start=1): # start counting from 1
print(index, fruit)
8.5 zip() -- loop over multiple lists together
names = ["A", "B", "C"]
marks = [80, 90, 70]
for name, mark in zip(names, marks):
print(name, mark)
COMMON ERROR: infinite loop (program never stops / freezes)
Cause: forgetting to update the loop variable in a while loop.
Fix: make sure the condition variable changes inside the loop body, e.g. count += 1.
9. Functions -- Complete
def greet(name):
"""Docstring: explains what the function does."""
return f"Hello, {name}!"
print(greet("Tgr"))
print(greet.__doc__) # prints the docstring
9.1 Default, positional, keyword arguments
def intro(name, age=18, city="Unknown"):
return f"{name}, {age}, {city}"
print(intro("Tgr"))
print(intro("Tgr", 20))
print(intro(name="Tgr", city="Vijayawada")) # keyword args, any order
9.2 *args and **kwargs
def add_all(*args):
return sum(args)
def show(**kwargs):
for k, v in [Link]():
print(k, v)
print(add_all(1, 2, 3))
show(name="Tgr", age=20)
9.3 Keyword-only arguments
def create_user(name, *, age, city): # everything after * must be passed by keyword
return f"{name}, {age}, {city}"
print(create_user("Tgr", age=20, city="AP"))
# create_user("Tgr", 20, "AP") -- TypeError: takes 1 positional argument
9.4 Lambda functions
square = lambda x: x * x
add = lambda a, b: a + b
print(square(5), add(2, 3))
9.5 Scope: LEGB rule (Local, Enclosing, Global, Built-in)
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
9.6 global and nonlocal keywords
count = 0
def increment():
global count
count += 1 # modifies the global variable directly
def outer():
x = 10
def inner():
nonlocal x # modifies the enclosing function's variable
x += 1
inner()
print(x) # 11
9.7 Closures
A closure is an inner function that remembers variables from its enclosing scope even after the outer
function has finished. Used in decorators and factory functions.
def make_multiplier(n):
def multiplier(x):
return x * n # remembers "n" even after make_multiplier() returns
return multiplier
times3 = make_multiplier(3)
print(times3(10)) # 30
9.8 Recursion
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
COMMON ERROR: RecursionError: maximum recursion depth exceeded
Cause: missing or wrong base case, so the function calls itself forever.
Fix: make sure the base case is reachable and correctly stops the recursion.
9.9 Type hints (optional but expected in professional code)
def add(a: int, b: int) -> int:
return a + b
def greet(name: str = "Guest") -> str:
return f"Hello, {name}"
from typing import List, Dict, Optional
def process(items: List[int]) -> Dict[str, int]:
return {"total": sum(items)}
def find_user(uid: int) -> Optional[str]: # may return None
return None
COMMON ERROR: TypeError: greet() missing 1 required positional argument: 'name'
Cause: calling a function without a required argument.
Fix: pass all required arguments, or give the parameter a default value.
10. Lists -- Complete
nums = [5, 2, 8, 1]
[Link](9) # add to end
[Link](0, 100) # insert at index
[Link]([20, 30]) # add multiple items
[Link](2) # remove first matching value
popped = [Link]() # remove & return last item
popped2 = [Link](0) # remove & return item at index 0
[Link]() # sort ascending, in place
[Link](reverse=True) # sort descending
[Link]() # reverse order, in place
print([Link](5)) # count occurrences of 5
print([Link](8)) # index of first occurrence of 8
[Link]() # empty the list
copy1 = [Link]() # shallow copy
copy2 = list(nums) # another way to copy
combined = [1, 2] + [3, 4] # concatenation -> [1,2,3,4]
repeated = [0] * 5 # -> [0,0,0,0,0]
sorted_new = sorted(nums) # returns NEW sorted list, doesn't modify original
COMMON ERROR: IndexError: list index out of range
Cause: accessing nums[10] on a 5-item list.
Fix: check len(nums) before indexing, or use try/except IndexError.
COMMON ERROR: ValueError: [Link](x): x not in list
Cause: calling .remove() with a value that doesn't exist in the list.
Fix: check with 'if x in list' first, or catch the ValueError.
List comprehensions (with condition and nested)
squares = [x*x for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
pairs = [(x, y) for x in range(3) for y in range(2)] # nested loop comprehension
labels = ["even" if x % 2 == 0 else "odd" for x in range(5)] # if/else inline
11. Tuples -- Complete
t = (1, 2, 3)
single = (5,) # a ONE-element tuple needs a trailing comma
not_a_tuple = (5) # this is just an int in parentheses!
print([Link](2)) # counts occurrences
print([Link](3)) # index of value
a, b, c = t # unpacking
COMMON ERROR: TypeError: 'tuple' object does not support item assignment
Cause: trying t[0] = 100 -- tuples are immutable.
Fix: convert to a list first if you need to modify it: list(t).
12. Dictionaries -- Complete
d = {"name": "Tgr", "age": 20}
d["city"] = "AP" # add
d["age"] = 21 # update
del d["city"] # delete a key
removed = [Link]("age") # remove & return value
print([Link]("name")) # safe access
print([Link]("missing", "N/A")) # safe access with default
print([Link]()) # dict_keys(['name'])
print([Link]()) # dict_values(['Tgr'])
print([Link]()) # dict_items([('name', 'Tgr')])
[Link]({"country": "India"}) # merge another dict in
[Link]("status", "active") # sets key only if it doesn't exist
for key, value in [Link]():
print(key, value)
COMMON ERROR: KeyError: 'age'
Cause: accessing d['age'] when the key doesn't exist.
Fix: use [Link]('age') instead, which returns None (or a default) instead of crashing.
Dict comprehension
squares = {x: x*x for x in range(5)}
filtered = {k: v for k, v in [Link]() if v > 5}
13. Sets -- Complete
s = {1, 2, 3}
[Link](4)
[Link](2) # raises KeyError if not present
[Link](10) # does NOT raise error if not present -- safer
a = {1, 2, 3}
b = {2, 3, 4}
print(a | b) # union
print(a & b) # intersection
print(a - b) # difference
print(a ^ b) # symmetric difference (in either, not both)
print([Link](b)) # is a fully contained in b?
unique = set([1, 1, 2, 2, 3]) # removes duplicates -> {1, 2, 3}
squares = {x*x for x in range(5)} # set comprehension
COMMON ERROR: TypeError: unhashable type: 'list'
Cause: trying to put a list inside a set (sets need hashable/immutable elements).
Fix: use a tuple instead of a list if you need to store a sequence in a set.
14. Errors and Exceptions -- Complete Reference
Errors in Python fall into two categories: Syntax Errors (your code isn't valid Python at all, caught before
running) and Exceptions (valid code that fails while running). Knowing the exact error name tells you exactly
what went wrong -- this is one of the most valuable debugging skills you can build.
14.1 Reading a traceback
When Python crashes, it prints a 'traceback' -- read it from the BOTTOM up. The last line names the
exception and the message; the lines above show the call chain that led there.
Traceback (most recent call last):
File "[Link]", line 10, in <module>
result = divide(10, 0)
File "[Link]", line 5, in divide
return a / b
ZeroDivisionError: division by zero
Read this as: 'ZeroDivisionError happened on line 5, inside divide(), which was called from line 10.' Always
start with the exception type on the last line.
14.2 try / except / else / finally -- full syntax
try:
age = int(input("Enter age: "))
result = 100 / age
except ValueError:
print("Not a valid number")
except ZeroDivisionError:
print("Age cannot be zero")
except Exception as e: # catches anything else -- always LAST
print(f"Unexpected error: {e}")
else:
print(f"Result: {result}") # runs ONLY if no exception occurred
finally:
print("Done") # ALWAYS runs, error or not -- cleanup code goes here
14.3 Catching multiple exceptions in one line
try:
x = int("abc")
except (ValueError, TypeError) as e:
print(f"Bad input: {e}")
14.4 Raising exceptions
def withdraw(balance, amount):
if amount < 0:
raise ValueError("Amount cannot be negative")
if amount > balance:
raise ValueError("Insufficient funds")
return balance - amount
try:
withdraw(100, -5)
except ValueError as e:
print(e)
14.5 Custom exceptions
Used in real projects to represent domain-specific errors clearly -- e.g. an e-commerce app might have
OutOfStockError, a banking app InsufficientFundsError.
class InsufficientFundsError(Exception):
"""Raised when a withdrawal exceeds the available balance."""
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError(f"Cannot withdraw {amount}, balance is {balance}")
return balance - amount
try:
withdraw(100, 500)
except InsufficientFundsError as e:
print("Transaction failed:", e)
14.6 The exception hierarchy (key built-in exceptions)
BaseException
+-- SystemExit
+-- KeyboardInterrupt # user pressed Ctrl+C
+-- Exception # base for almost everything you'll catch
+-- ArithmeticError
| +-- ZeroDivisionError
+-- LookupError
| +-- IndexError
| +-- KeyError
+-- ValueError
+-- TypeError
+-- NameError
+-- AttributeError
+-- ImportError
| +-- ModuleNotFoundError
+-- OSError
| +-- FileNotFoundError
| +-- PermissionError
+-- StopIteration
+-- RecursionError
+-- MemoryError
+-- NotImplementedError
Because more specific errors inherit from Exception, catching 'except Exception' catches everything below
it. Always catch the MOST SPECIFIC error you expect first, and use a broad 'except Exception' only as a
last-resort catch-all.
15. Every Common Error, Explained
SyntaxError
The code isn't valid Python -- caught before the program even starts running.
if True
print("hi")
# SyntaxError: expected ':' -- missing colon after the if condition
IndentationError
if True:
print("hi")
# IndentationError: expected an indented block -- missing indent after the colon
NameError
print(total)
# NameError: name 'total' is not defined -- used before assignment, or a typo
TypeError
"5" + 5
# TypeError: can only concatenate str (not "int") to str
# Fix: str(5) + "5" or int("5") + 5
ValueError
int("abc")
# ValueError: invalid literal for int() with base 10: 'abc'
# Fix: validate the string is numeric before converting
IndexError
nums = [1, 2, 3]
print(nums[5])
# IndexError: list index out of range
KeyError
d = {"a": 1}
print(d["b"])
# KeyError: 'b' -- Fix: use [Link]('b') instead
AttributeError
x = 5
[Link](3)
# AttributeError: 'int' object has no attribute 'append'
# Cause: calling a method that doesn't exist for that type (append is for lists)
ZeroDivisionError
print(10 / 0)
# ZeroDivisionError: division by zero
ImportError / ModuleNotFoundError
import some_missing_library
# ModuleNotFoundError: No module named 'some_missing_library'
# Fix: pip install some_missing_library
FileNotFoundError
open("[Link]")
# FileNotFoundError: [Errno 2] No such file or directory: '[Link]'
RecursionError
def f():
return f() # no base case
f()
# RecursionError: maximum recursion depth exceeded
StopIteration
it = iter([1, 2])
next(it); next(it); next(it)
# StopIteration -- raised when a generator/iterator has no more values
# (normally handled automatically by for-loops)
KeyboardInterrupt
Raised when the user presses Ctrl+C to stop a running program. Common to catch this in long-running
scripts to shut down cleanly.
try:
while True:
pass
except KeyboardInterrupt:
print("Stopped by user")
16. File Handling -- Complete
16.1 File modes
'r' read (default, error if file doesn't exist)
'w' write (creates file, OVERWRITES if it exists)
'a' append (creates file, adds to end if it exists)
'x' exclusive create (errors if file already exists)
'r+' read and write
'rb', 'wb' binary mode (images, non-text files)
with open("[Link]", "w") as f:
[Link]("Line 1\n")
[Link](["Line 2\n", "Line 3\n"])
with open("[Link]", "r") as f:
print([Link]()) # whole file as one string
with open("[Link]", "r") as f:
lines = [Link]() # list of lines, each with \n
with open("[Link]", "r") as f:
for line in f: # memory-efficient, one line at a time
print([Link]())
COMMON ERROR: FileNotFoundError
Cause: opening a file in 'r' mode that doesn't exist, or wrong path.
Fix: check the path with [Link](path) first, or catch the exception.
16.2 pathlib -- the modern way to handle paths
from pathlib import Path
p = Path("data/[Link]")
print([Link]()) # True/False
print([Link]) # [Link]
print([Link]) # .txt
print([Link]) # data
for file in Path(".").glob("*.py"): # list all .py files in current folder
print(file)
16.3 JSON (used constantly with APIs)
import json
data = {"name": "Tgr", "age": 20, "skills": ["Python", "C++"]}
with open("[Link]", "w") as f:
[Link](data, f, indent=2) # write dict as JSON file
with open("[Link]", "r") as f:
loaded = [Link](f) # read JSON file back into a dict
text = [Link](data) # dict -> JSON string
back = [Link](text) # JSON string -> dict
16.4 CSV files
import csv
with open("[Link]", "w", newline="") as f:
writer = [Link](f)
[Link](["name", "marks"])
[Link](["Tgr", 85])
with open("[Link]", "r") as f:
reader = [Link](f)
for row in reader:
print(row["name"], row["marks"])
17. Modules and Packages -- Complete
# math_utils.py
def add(a, b):
return a + b
PI = 3.14159
if __name__ == "__main__":
# this block only runs when math_utils.py is run directly,
# NOT when it's imported by another file -- used for quick self-tests
print(add(2, 3))
# [Link]
import math_utils
print(math_utils.add(5, 3))
print(math_utils.PI)
from math_utils import add # import a specific name
from math_utils import add as plus # import with a different name
from math_utils import * # import everything (avoid in real projects)
COMMON ERROR: ModuleNotFoundError: No module named 'math_utils'
Cause: running [Link] from a different folder than math_utils.py.
Fix: run scripts from the same folder, or set up the project as a proper package.
17.1 Installing and managing packages
pip install requests
pip install requests==2.31.0 # specific version
pip install --break-system-packages requests # needed on some Termux setups
pip list # show installed packages
pip freeze > [Link] # save installed packages to a file
pip install -r [Link] # install everything from that file
17.2 Virtual environments
Keeps each project's installed packages separate so different projects don't conflict.
python -m venv myenv
source myenv/bin/activate # Linux/Mac/Termux
myenv\Scripts\activate # Windows
pip install requests
deactivate
17.3 Useful standard library modules
import os # interact with the operating system, file paths, env vars
import sys # command-line arguments, exit the program
import random # random numbers, random choice from a list
import math # sqrt, floor, ceil, pi, trig functions
import time # sleep, measure elapsed time
import datetime # dates and times
import re # regular expressions
import collections # Counter, defaultdict, deque, namedtuple
import itertools # combinations, permutations, infinite iterators
import functools # reduce, lru_cache, wraps
18. Object-Oriented Programming -- Complete
18.1 Class basics
class Student:
school_name = "AP Public School" # class variable -- shared by ALL instances
def __init__(self, name, marks): # constructor
[Link] = name # instance variable -- unique per object
[Link] = marks
def is_passing(self): # instance method
return [Link] >= 40
s1 = Student("Tgr", 78)
s2 = Student("Priya", 30)
print(s1.school_name, s2.school_name) # same for both -- class variable
print(s1.is_passing(), s2.is_passing())
18.2 Instance vs class vs static methods
class Counter:
total = 0
def __init__(self):
[Link] += 1
@classmethod
def get_total(cls): # works on the CLASS, not an instance
return [Link]
@staticmethod
def is_valid_count(n): # doesn't need self or cls -- just a utility
return n >= 0
Counter(); Counter(); Counter()
print(Counter.get_total()) # 3
print(Counter.is_valid_count(-1)) # False
18.3 Inheritance and super()
class Person:
def __init__(self, name):
[Link] = name
def greet(self):
return f"Hi, I am {[Link]}"
class Employee(Person):
def __init__(self, name, salary):
super().__init__(name) # calls Person's __init__
[Link] = salary
def greet(self): # method overriding
return f"{super().greet()}, I earn {[Link]}"
e = Employee("Tgr", 50000)
print([Link]())
print(isinstance(e, Person)) # True -- Employee IS-A Person
18.4 Multiple inheritance
class Flyer:
def fly(self):
return "Flying"
class Swimmer:
def swim(self):
return "Swimming"
class Duck(Flyer, Swimmer): # inherits from both
pass
d = Duck()
print([Link](), [Link]())
18.5 Encapsulation (public, protected, private)
class Account:
def __init__(self, balance):
[Link] = balance # public -- accessible anywhere
self._pin = "1234" # protected -- convention: internal use only
self.__secret_key = "xyz" # private -- name-mangled, hardest to access
a = Account(1000)
print([Link]) # works fine
print(a._pin) # works but breaks convention (shouldn't touch it)
# print(a.__secret_key) # AttributeError -- name is mangled to _Account__secret_key
18.6 Polymorphism and abstraction
from abc import ABC, abstractmethod
class Shape(ABC): # abstract base class -- cannot be instantiated
@abstractmethod
def area(self):
pass # forces every subclass to implement this
class Circle(Shape):
def __init__(self, radius):
[Link] = radius
def area(self):
return 3.14159 * [Link] ** 2
class Square(Shape):
def __init__(self, side):
[Link] = side
def area(self):
return [Link] ** 2
shapes = [Circle(5), Square(4)]
for s in shapes:
print([Link]()) # each shape computes area its own way
# Shape() -- TypeError: Can't instantiate abstract class Shape
18.7 Properties (@property) -- controlled attribute access
Lets a method be accessed like a plain attribute, useful for validation or computed values.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@[Link]
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self): # computed, read-only property
return 3.14159 * self._radius ** 2
c = Circle(5)
print([Link]) # 5 -- accessed like an attribute, not [Link]()
[Link] = 10 # calls the setter, runs validation
print([Link])
18.8 Dunder (magic) methods -- full common set
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __str__(self): # print(obj) / str(obj) -- human-readable
return f"({self.x}, {self.y})"
def __repr__(self): # developer-facing representation, used in debugging
return f"Point({self.x}, {self.y})"
def __eq__(self, other): # controls ==
return self.x == other.x and self.y == other.y
def __add__(self, other): # controls +
return Point(self.x + other.x, self.y + other.y)
def __len__(self): # controls len(obj)
return 2
def __lt__(self, other): # controls < , used by sort()
return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
p1, p2 = Point(1, 2), Point(3, 4)
print(p1 + p2) # (4, 6)
print(p1 == Point(1, 2)) # True
18.9 Dataclasses (Python 3.7+) -- less boilerplate
Automatically generates __init__, __repr__, and __eq__ for simple data-holding classes.
from dataclasses import dataclass
@dataclass
class Student:
name: str
marks: int
city: str = "Unknown" # default value
s = Student("Tgr", 85)
print(s) # Student(name='Tgr', marks=85, city='Unknown')
print(s == Student("Tgr", 85)) # True -- __eq__ generated automatically
19. Iterators and Generators -- Complete
19.1 The iterator protocol
nums = [1, 2, 3]
it = iter(nums) # get an iterator from an iterable
print(next(it)) # 1
print(next(it)) # 2
print(next(it)) # 3
# print(next(it)) -- StopIteration, no more items
class CountUpTo: # a custom iterator class
def __init__(self, limit):
[Link] = limit
self.n = 0
def __iter__(self):
return self
def __next__(self):
if self.n >= [Link]:
raise StopIteration
self.n += 1
return self.n
for num in CountUpTo(5):
print(num)
19.2 Generators (yield) -- the simpler way
def count_up_to(limit):
n = 1
while n <= limit:
yield n # pauses here, resumes on next call
n += 1
for num in count_up_to(5):
print(num)
gen = (x*x for x in range(1000000)) # generator expression -- lazy, memory-safe
print(next(gen))
20. Decorators -- Complete
import functools
def timer(func):
@[Link](func) # preserves original function's name/docstring
def wrapper(*args, **kwargs):
import time
start = [Link]()
result = func(*args, **kwargs)
print(f"{func.__name__} took {[Link]()-start:.4f}s")
return result
return wrapper
@timer
def slow():
import time; [Link](1)
slow()
Decorators that take arguments
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi")
say_hi() # prints "Hi" 3 times
21. Context Managers -- Complete
class ManagedFile:
def __init__(self, filename):
[Link] = filename
def __enter__(self):
[Link] = open([Link], "w")
return [Link]
def __exit__(self, exc_type, exc_value, traceback):
[Link]() # always runs, even if an error happened inside
with ManagedFile("[Link]") as f:
[Link]("hello")
# Simpler way using contextlib
from contextlib import contextmanager
@contextmanager
def managed_file(filename):
f = open(filename, "w")
try:
yield f
finally:
[Link]()
with managed_file("[Link]") as f:
[Link]("hello")
22. Functional Tools -- map, filter, reduce
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x*2, nums))
print(doubled) # [2, 4, 6, 8, 10]
evens = list(filter(lambda x: x % 2 == 0, nums))
print(evens) # [2, 4]
from functools import reduce
total = reduce(lambda a, b: a + b, nums)
print(total) # 15
from functools import lru_cache
@lru_cache(maxsize=None) # caches results -- speeds up repeated calls
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
print(fib(30)) # fast, thanks to caching
23. Regular Expressions (re module)
Used for: validating input (emails, phone numbers), searching/extracting patterns from text, log file parsing,
web scraping cleanup.
import re
text = "Contact: tgr@[Link] or 9876543210"
email = [Link](r"[\w.]+@[\w.]+", text)
print([Link]()) # tgr@[Link]
phone = [Link](r"\d{10}", text)
print(phone) # ['9876543210']
cleaned = [Link](r"\d", "", text) # remove all digits
print(cleaned)
if [Link](r"^[A-Za-z]+$", "Hello"): # matches from the START
print("Only letters")
Common patterns: \d digit, \w word character, \s whitespace, + one-or-more, * zero-or-more, ? optional, ^
start, $ end, [] character set.
24. The collections Module
from collections import Counter, defaultdict, namedtuple, deque
words = ["cat", "dog", "cat", "bird", "dog", "cat"]
print(Counter(words)) # Counter({'cat': 3, 'dog': 2, 'bird': 1})
print(Counter(words).most_common(2)) # top 2 most frequent
graph = defaultdict(list) # auto-creates a default value for missing keys
graph["A"].append("B") # no KeyError even though "A" wasn't set first
Point = namedtuple("Point", ["x", "y"]) # lightweight class-like tuple
p = Point(1, 2)
print(p.x, p.y)
queue = deque([1, 2, 3]) # fast append/pop from BOTH ends
[Link](0)
[Link](4)
print(queue) # deque([0, 1, 2, 3, 4])
25. Dates and Time
from datetime import datetime, timedelta
now = [Link]()
print(now)
print([Link]("%Y-%m-%d %H:%M:%S")) # format as string
parsed = [Link]("2026-07-16", "%Y-%m-%d") # string to datetime
tomorrow = now + timedelta(days=1)
print(tomorrow)
26. Testing and Debugging
26.1 assert -- quick sanity checks
def add(a, b):
return a + b
assert add(2, 3) == 5, "add() is broken" # raises AssertionError if False
26.2 pytest -- the standard testing framework
# test_math.py
def add(a, b):
return a + b
def test_add_positive():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
# Run with: pytest test_math.py -v
26.3 Debugging with print and pdb
import pdb
def buggy_function(x):
pdb.set_trace() # execution pauses here -- drops into interactive debugger
return x * 2
# pdb commands once paused: n (next line), c (continue), p x (print x), q (quit)
26.4 Logging (better than scattering print statements)
import logging
[Link](level=[Link])
[Link]("Detailed info, hidden by default")
[Link]("General info")
[Link]("Something looks off")
[Link]("Something failed")
27. Concurrency (Threading, Multiprocessing, Async) --
Overview
Used when a program needs to do multiple things at once -- download several files in parallel, handle many
web requests, or run CPU-heavy work faster.
import threading
def worker(n):
print(f"Worker {n} running")
threads = [[Link](target=worker, args=(i,)) for i in range(3)]
for t in threads: [Link]()
for t in threads: [Link]() # wait for all to finish
# asyncio -- for I/O-heavy tasks like network requests
import asyncio
async def fetch_data():
print("Fetching...")
await [Link](1) # simulates waiting on a network call
print("Done")
[Link](fetch_data())
Rule of thumb: threading for I/O-bound tasks, multiprocessing for CPU-bound tasks (Python's GIL limits true
parallel CPU work in threads), asyncio for many concurrent network/IO operations without the overhead of
real threads.
28. Key Libraries for Real Projects
import numpy as np
arr = [Link]([1, 2, 3])
print(arr * 2, [Link]())
import pandas as pd
df = [Link]({"name": ["A", "B"], "marks": [80, 60]})
print(df[df["marks"] > 70])
import requests
r = [Link]("[Link]
print(r.status_code, [Link]())
from sklearn.linear_model import LinearRegression
model = LinearRegression()
# [Link](X_train, y_train)
# [Link](X_test)
29. Practice Roadmap
Week 1: Syntax, variables, data types, operators, strings.
Week 2: Conditionals, loops, error handling basics -- solve 20 easy problems.
Week 3: Functions, closures, decorators -- rewrite earlier scripts using functions.
Week 4: Lists, tuples, dicts, sets, comprehensions -- build a contact book / grade tracker.
Week 5: File handling, JSON, CSV, custom exceptions -- make the tracker save/load data.
Week 6: Full OOP -- rebuild the tracker using classes, properties, and inheritance.
Week 7: Iterators, generators, context managers, regex.
Week 8: collections module, datetime, testing with pytest, basic logging.
After week 8: NumPy, Pandas, and into ML basics -- this slots into your 60-day and 12-15 month plans
already in place.