Python Complete Notes
Python Complete Notes
This guide takes you from writing your first line of Python to understanding advanced concepts like decorators,
generators, concurrency, and metaclasses. Every chapter has explanations, runnable examples, and practice
problems.
Page 1
CHAPTER 1: GETTING STARTED
Python is a high-level, interpreted, general-purpose programming language known for its readable syntax
and huge ecosystem of libraries. It is used in web development, data science, automation, AI/ML, scripting,
and more.
Key features:
# Linux (Debian/Ubuntu)
sudo apt install python3
Page 2
# 3. Using Jupyter notebooks / IDEs (VS Code, PyCharm)
print("Hello, World!")
Practice Problems 1
1. Install Python on your machine and confirm the version using the terminal.
2. Write a program that prints your name, age, and favorite programming language on three separate
lines.
3. Use the REPL to compute 7 * 6 and 2 ** 10.
Page 3
CHAPTER 2: VARIABLES AND DATA TYPES
2.1 Variables
A variable is a name that refers to a value stored in memory. Python variables do not need explicit type
declarations.
name = "Alice"
age = 25
height = 5.6
is_student = True
Naming rules:
x = 10 # int
y = 3.14 # float
name = "Bob" # str
flag = True # bool
z = None # NoneType
Page 4
2.3 Type Conversion
a = "10"
b = int(a) # string to int
c = float(a) # string to float
d = str(25) # int to string
e = bool(0) # 0 -> False, any nonzero -> True
Practice Problems 2
1. Create variables for a product's name, price, and quantity, then print a formatted receipt line.
2. Write a program that asks the user for two numbers and prints their sum, difference, product, and
quotient.
3. What is the output of type(5 / 2) versus type(5 // 2)? Explain the difference.
4. Convert the string "3.14159" to a float and round it to 2 decimal places.
Page 5
CHAPTER 3: OPERATORS
a, b = 10, 3
print(a + b) # 13 addition
print(a - b) # 7 subtraction
print(a * b) # 30 multiplication
print(a / b) # 3.333... true division
print(a // b) # 3 floor division
print(a % b) # 1 modulus (remainder)
print(a ** b) # 1000 exponentiation
print(5 == 5) # True
print(5 != 3) # True
print(5 > 3) # True
print(5 < 3) # False
print(5 >= 5) # True
print(5 <= 4) # False
x = 5
x += 3 # x = x + 3 -> 8
x -= 2 # x = x - 2 -> 6
x *= 2 # x = x * 2 -> 12
x /= 4 # x = x / 4 -> 3.0
x //= 1 # floor divide assign
x **= 2 # power assign
x %= 5 # modulus assign
Page 6
a = [1, 2, 3]
b = a
c = [1, 2, 3]
print(2 in a) # True
print(5 not in a) # True
Practice Problems 3
1. Write a program to check whether a number is even or odd using the modulus operator.
2. Evaluate 17 // 5, 17 % 5, and 17 ** 2 without running code, then check your answer.
3. Explain the difference between is and == with an example.
4. Write a program that swaps two variables' values without using a third variable.
Page 7
CHAPTER 4: STRINGS
s = "Hello, Python!"
print(s[0]) # H
print(s[-1]) # !
print(s[0:5]) # Hello (slicing)
print(s[::-1]) # reversed string
print(len(s)) # length of string
# .format() method
print("{} is {} years old".format(name, age))
Page 8
4.4 Strings Are Immutable
s = "hello"
# s[0] = "H" # This raises TypeError!
s = "H" + s[1:] # Create a new string instead
Practice Problems 4
1. Write a function that checks if a given string is a palindrome (ignore case and spaces).
2. Given a sentence, count how many vowels it contains.
3. Write a program to reverse the words in a sentence (not the letters), e.g., "I love Python" -> "Python
love I".
4. Format the number 1234567.891 to display as "1,234,567.89".
Page 9
CHAPTER 5: CONTROL FLOW
age = 20
if age < 13:
print("Child")
elif age < 20:
print("Teenager")
else:
print("Adult")
age = 20
status = "Adult" if age >= 18 else "Minor"
count = 0
while count < 5:
print(count)
count += 1
else:
print("Loop finished normally") # runs if no 'break' occurred
Page 10
5.5 break, continue, pass
for i in range(10):
if i == 5:
break # exits the loop
if i % 2 == 0:
continue # skips to next iteration
print(i)
if True:
pass # placeholder, does nothing
for i in range(3):
for j in range(3):
print(i, j)
Practice Problems 5
1. Print all numbers from 1 to 100 that are divisible by 3 or 5.
2. Write a program that prints the multiplication table of a number entered by the user.
3. Implement FizzBuzz: print numbers 1-100, but print "Fizz" for multiples of 3, "Buzz" for multiples of 5,
and "FizzBuzz" for multiples of both.
4. Use a while loop to find the sum of digits of a given number.
Page 11
CHAPTER 6: DATA STRUCTURES
6.1 Lists
6.2 Tuples
point = (3, 4)
x, y = point # unpacking
print(point[0])
6.3 Dictionaries
Page 12
print(list([Link]()))
6.4 Sets
a = {1, 2, 3}
b = {3, 4, 5}
[Link](10)
[Link](1)
Practice Problems 6
1. Given a list of numbers, write code to remove duplicates while preserving order.
2. Merge two dictionaries such that values from the second override the first.
3. Given a list of words, build a dictionary counting how many times each word appears.
4. Write a program to find the common elements between two lists using sets.
5. Create a tuple of coordinates and write a function that computes the distance between two points.
Page 13
CHAPTER 7: COMPREHENSIONS
Practice Problems 7
1. Use a list comprehension to create a list of squares of even numbers from 1 to 20.
2. Flatten a nested list [[1,2],[3,4],[5,6]] into [1,2,3,4,5,6] using a comprehension.
3. Create a dictionary mapping each word in a sentence to its length.
4. Convert a Fahrenheit-to-Celsius conversion into a one-line list comprehension for a list of
temperatures.
Page 14
CHAPTER 8: FUNCTIONS
def greet(name):
"""Return a greeting message."""
return f"Hello, {name}!"
print(greet("Alice"))
8.3 Scope
x = 10 # global
def modify():
global x
x = 20
def outer():
y = 5
def inner():
nonlocal y
y += 1
inner()
return y
Page 15
square = lambda x: x**2
add = lambda a, b: a + b
print(square(5)) # 25
8.5 Recursion
def factorial(n):
if n <= 1:
return 1
return n * factorial(n - 1)
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
Practice Problems 8
1. Write a function is_prime(n) that returns True if n is a prime number.
2. Write a recursive function to compute the nth Fibonacci number, then rewrite it using memoization.
3. Write a function that accepts any number of numeric arguments and returns their average.
4. Write a function apply_discount(price, discount=0.1) and use type hints.
5. Use a lambda function with sorted() to sort a list of dictionaries by a specific key.
Page 16
CHAPTER 9: OBJECT-ORIENTED PROGRAMMING
class Dog:
species = "Canis familiaris" # class attribute
def bark(self):
return f"{[Link]} says Woof!"
d = Dog("Rex", 3)
print([Link]())
print([Link])
9.2 Inheritance
class Animal:
def __init__(self, name):
[Link] = name
def speak(self):
raise NotImplementedError
class Cat(Animal):
def speak(self):
return f"{[Link]} says Meow!"
class Dog(Animal):
def speak(self):
return f"{[Link]} says Woof!"
9.3 Encapsulation
class Account:
def __init__(self, balance):
self._balance = balance # protected (convention)
Page 17
self.__pin = "1234" # private (name-mangled)
@property
def balance(self):
return self._balance
@[Link]
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self._balance = value
acc = Account(100)
[Link] = 200
print([Link])
class Circle:
pi = 3.14159
def area(self):
return [Link] * [Link] ** 2
@classmethod
def unit_circle(cls):
return cls(1)
@staticmethod
def is_valid_radius(r):
return r > 0
def __str__(self):
return f"Circle(radius={[Link]})"
def __repr__(self):
return f"Circle({[Link]!r})"
c1 = Circle(5)
print(c1) # uses __str__
print(c1 + Circle(2)) # uses __add__
Page 18
9.5 Abstract Base Classes and Multiple Inheritance
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Square(Shape):
def __init__(self, side):
[Link] = side
def area(self):
return [Link] ** 2
class Flyer:
def fly(self):
return "Flying"
class Swimmer:
def swim(self):
return "Swimming"
d = Duck()
print([Link](), [Link]()) # multiple inheritance
Practice Problems 9
1. Design a Vehicle base class with subclasses Car and Motorcycle that each override a
describe() method.
2. Create a BankAccount class with deposit(), withdraw(), and a property to prevent negative
balances.
3. Implement __eq__, __lt__, and __str__ for a Point(x, y) class so points can be compared and
sorted.
4. Explain the difference between a classmethod and a staticmethod with an example of each.
5. Create an abstract Shape class with area() and perimeter() methods, then implement
Rectangle and Circle.
Page 19
CHAPTER 10: MODULES AND PACKAGES
import math
print([Link](16))
# file: [Link]
def greet(name):
return f"Hello {name}"
# file: [Link]
import mymodule
print([Link]("Alice"))
10.3 Packages
def main():
print("Running as script")
if __name__ == "__main__":
main()
This ensures code only runs when the file is executed directly, not when imported.
Page 20
10.5 Virtual Environments and pip
Practice Problems 10
1. Create a module [Link] with functions for area of a circle, square, and triangle, then import
and use it.
2. Explain why the if __name__ == "__main__": guard is useful.
3. Create a virtual environment and install the requests package inside it.
4. Organize three related modules into a package with a proper __init__.py.
Page 21
CHAPTER 11: FILE HANDLING
# Writing
with open("[Link]", "w") as f:
[Link]("Hello, file!\n")
[Link](["line1\n", "line2\n"])
# Reading
with open("[Link]", "r") as f:
content = [Link]() # entire file as string
The with statement automatically closes the file, even if an error occurs.
import csv
import json
Page 22
with open("[Link]", "r") as f:
loaded = [Link](f)
p = Path("folder/subfolder/[Link]")
print([Link]) # [Link]
print([Link]) # folder/subfolder
print([Link]) # .txt
print([Link]())
[Link](parents=True, exist_ok=True)
Practice Problems 11
1. Write a program that reads a text file and counts the number of words, lines, and characters.
2. Write student records (name, grade) to a CSV file, then read them back and print the average grade.
3. Save a list of dictionaries as JSON, then load it back and print each entry.
4. Use pathlib to list all .txt files in a directory.
Page 23
CHAPTER 12: EXCEPTION HANDLING
try:
num = int(input("Enter a number: "))
result = 10 / num
except ValueError:
print("That's not a valid number!")
except ZeroDivisionError:
print("Cannot divide by zero!")
else:
print(f"Result: {result}") # runs if no exception occurred
finally:
print("Execution complete.") # always runs
class InsufficientFundsError(Exception):
"""Raised when a withdrawal exceeds the balance."""
pass
try:
withdraw(100, 150)
except InsufficientFundsError as e:
print(e)
Page 24
Catch the most specific exception first, then more general ones. Avoid bare except: clauses, since they
hide bugs. Use except Exception as e: if you need a general catch-all.
Practice Problems 12
1. Write a program that repeatedly asks for a number until valid input is given, using try/except.
2. Create a custom exception NegativeValueError and raise it inside a function that computes a
square root.
3. Write a function that safely opens a file and handles FileNotFoundError gracefully.
4. Explain the difference between except Exception and a bare except:.
Page 25
CHAPTER 13: ITERATORS AND GENERATORS
nums = [1, 2, 3]
it = iter(nums)
print(next(it)) # 1
print(next(it)) # 2
class Counter:
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 x in Counter(3):
print(x) # 1 2 3
13.2 Generators
Generators produce values lazily using yield, which is memory-efficient for large sequences.
def countdown(n):
while n > 0:
yield n
n -= 1
for x in countdown(5):
print(x)
def infinite_evens():
n = 0
while True:
yield n
n += 2
gen = infinite_evens()
print(next(gen), next(gen), next(gen)) # 0 2 4
Page 26
13.3 Generator Expressions and itertools
import itertools
print(list([Link]([Link](1), 5))) # [1,2,3,4,5]
print(list([Link]([1,2], [3,4]))) # [1,2,3,4]
print(list([Link]([1,2,3], 2)))
print(list([Link]([1,2,3], 2)))
Practice Problems 13
1. Write a generator function that yields the Fibonacci sequence indefinitely.
2. Write a custom iterator class that iterates over even numbers up to a limit.
3. Use [Link] to list all 2-element combinations from ["a","b","c","d"].
4. Explain the memory advantage of a generator expression over a list comprehension for large datasets.
Page 27
CHAPTER 14: DECORATORS
def shout(text):
return [Link]()
def whisper(text):
return [Link]()
def greet(func):
return func("Hello!")
print(greet(shout)) # HELLO!
print(greet(whisper)) # hello!
import functools
import time
def timer(func):
@[Link](func)
def wrapper(*args, **kwargs):
start = [Link]()
result = func(*args, **kwargs)
end = [Link]()
print(f"{func.__name__} took {end - start:.4f}s")
return result
return wrapper
@timer
def slow_function():
[Link](1)
slow_function()
def repeat(times):
def decorator(func):
@[Link](func)
def wrapper(*args, **kwargs):
for _ in range(times):
Page 28
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
class MyClass:
@staticmethod
def utility():
return "static"
@classmethod
def create(cls):
return cls()
@property
def value(self):
return self._value
Practice Problems 14
1. Write a decorator @log_calls that prints the function name and arguments every time it's called.
2. Write a decorator that caches results of a slow function (a basic memoization decorator).
3. Write a decorator with an argument that only allows a function to run if a user is "authenticated"
(simulate with a boolean).
4. Explain why [Link] is used inside decorators.
Page 29
CHAPTER 15: CONTEXT MANAGERS
with open("[Link]") as f:
data = [Link]()
# file automatically closed here
class Timer:
def __enter__(self):
import time
[Link] = [Link]()
return self
with Timer():
sum(range(1000000))
@contextmanager
def open_resource(name):
print(f"Opening {name}")
yield name
print(f"Closing {name}")
Practice Problems 15
1. Write a context manager that temporarily changes the working directory and restores it afterward.
2. Write a context manager using @contextmanager that suppresses a specific exception type.
3. Explain what __enter__ and __exit__ are responsible for.
Page 30
Page 31
CHAPTER 16: FUNCTIONAL PROGRAMMING
nums = [1, 2, 3, 4, 5]
@lru_cache(maxsize=None)
def fib(n):
if n <= 1:
return n
return fib(n-1) + fib(n-2)
16.3 Closures
def make_multiplier(factor):
def multiply(x):
return x * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5), triple(5)) # 10 15
Practice Problems 16
1. Use map() and filter() together to get the squares of only the odd numbers in a list.
2. Use [Link]() to find the maximum value in a list without using max().
Page 32
3. Write a closure-based counter function that remembers its count between calls.
4. Use lru_cache to speed up a recursive Fibonacci function and measure the difference.
Page 33
CHAPTER 17: REGULAR EXPRESSIONS
import re
Pattern Meaning
\d digit
\w word character
\s whitespace
+ one or more
* zero or more
? zero or one
^ start of string
$ end of string
Practice Problems 17
1. Write a regex to validate whether a string is a valid email address.
Page 34
2. Extract all hashtags (e.g., "#python") from a block of text.
3. Write a regex to check if a password contains at least one uppercase, one lowercase, one digit, and is
8+ characters.
4. Use [Link] to mask all digits in a string of credit card numbers with "*".
Page 35
CHAPTER 18: WORKING WITH DATES AND TIME
now = [Link]()
print([Link]("%Y-%m-%d %H:%M:%S"))
d1 = datetime(2024, 1, 1)
d2 = datetime(2024, 12, 31)
print((d2 - d1).days) # difference in days
Practice Problems 18
1. Write a program that calculates someone's age given their birth date.
2. Write a function that returns the date 30 days from today.
3. Parse the string "15/08/2024" into a datetime object.
Page 36
CHAPTER 19: CONCURRENCY — THREADING,
MULTIPROCESSING, ASYNCIO
import threading
import time
def worker(n):
[Link](1)
print(f"Worker {n} done")
threads = []
for i in range(5):
t = [Link](target=worker, args=(i,))
[Link](t)
[Link]()
for t in threads:
[Link]()
Note: Python's Global Interpreter Lock (GIL) means threads don't achieve true CPU parallelism, but they
work well for I/O-bound tasks (network calls, file I/O).
def square(n):
return n * n
if __name__ == "__main__":
with Pool(4) as p:
results = [Link](square, range(10))
print(results)
Multiprocessing creates separate processes, bypassing the GIL, and is suited for CPU-heavy work.
import asyncio
Page 37
print(f"Start {n}")
await [Link](1)
print(f"Done {n}")
return n * 2
[Link](main())
Practice Problems 19
1. Explain the difference between threading, multiprocessing, and asyncio, and when you'd use each.
2. Write a multithreaded program that downloads (simulate with sleep) 5 "files" concurrently.
3. Write an asyncio program that runs 3 asynchronous tasks concurrently and prints total time taken.
4. Use multiprocessing to compute the square of numbers 1-1,000,000 faster than a plain loop.
Page 38
CHAPTER 20: TESTING
import unittest
class TestMath([Link]):
def test_add_positive(self):
[Link](add(2, 3), 5)
def test_add_negative(self):
[Link](add(-1, -1), -2)
if __name__ == "__main__":
[Link]()
# file: test_math.py
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
$ pytest test_math.py -v
import pytest
@[Link]
def sample_data():
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
assert sum(sample_data) == 15
Page 39
@[Link]("a,b,expected", [(1,2,3), (0,0,0), (-1,1,0)])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected
Practice Problems 20
1. Write unit tests for a Calculator class covering add, subtract, multiply, and divide (including division
by zero).
2. Convert your unittest tests into pytest-style tests.
3. Write a parametrized pytest test for a is_prime() function using at least 5 cases.
Page 40
CHAPTER 21: ADVANCED TOPICS
21.1 Descriptors
class PositiveNumber:
def __set_name__(self, owner, name):
[Link] = "_" + name
class Product:
price = PositiveNumber()
def __init__(self, price):
[Link] = price
21.2 Metaclasses
class Meta(type):
def __new__(mcs, name, bases, namespace):
namespace["created_by"] = "Meta"
return super().__new__(mcs, name, bases, namespace)
class MyClass(metaclass=Meta):
pass
print(MyClass.created_by) # "Meta"
Metaclasses control how classes themselves are created. They are rarely needed in everyday code but
power frameworks like Django's ORM.
@dataclass
class Point:
x: float
y: float
Page 41
tags: list = field(default_factory=list)
def distance_from_origin(self):
return (self.x**2 + self.y**2) ** 0.5
p = Point(3, 4)
print(p) # auto-generated __repr__
print(p.distance_from_origin())
Run mypy to statically check type hints for errors before runtime.
Python uses reference counting plus a cyclic garbage collector to manage memory. The Global Interpreter
Lock (GIL) ensures only one thread executes Python bytecode at a time, which simplifies memory
management but limits CPU-bound multithreading (use multiprocessing instead).
import sys
x = [1, 2, 3]
print([Link](x))
import gc
[Link]() # force garbage collection
import requests
response = [Link]("[Link]
if response.status_code == 200:
data = [Link]()
print(data["login"])
Page 42
payload = {"name": "Alice"}
response = [Link]("[Link] json=payload)
my_project/
src/
my_project/
__init__.py
[Link]
tests/
test_core.py
[Link]
[Link]
Practice Problems 21
1. Create a @dataclass called Book with title, author, and price fields, and a method to apply a
discount.
2. Write a descriptor that ensures a string attribute is always stored in uppercase.
3. Add type hints to three functions you wrote in earlier chapters and run mypy to check them.
4. Use the requests library to fetch data from a public API and print a formatted summary.
5. Explain in your own words what the GIL is and why multiprocessing bypasses it.
Page 43
APPENDIX A: PYTHON STYLE (PEP 8) QUICK
REFERENCE
Page 44
APPENDIX B: COMMON BUILT-IN FUNCTIONS
CHEAT SHEET
Function Purpose
print(sorted(names))
print(any(age > 28 for age in ages))
print(all(age > 18 for age in ages))
Page 45
APPENDIX C: NEXT STEPS
Practice consistently — build small projects (a to-do app, a web scraper, a simple API) to reinforce every
concept in these notes.
Page 46