Comprehensive Python Documentation
Complete Python Guide
From print("Hello World") to metaclasses and asynchronous programming, this documentation covers everything you need to master Python — practical examples, deep explanations, and best practices.
1 Introduction to Python
What is Python and why should you learn it?
Python is a high-level, interpreted, and multi-paradigm programming language created by Guido van Rossum in 1991. The philosophical design of Python focuses on code readability and simple syntax — so much so that Python code resembles English pseudocode.
Key Features
- Clean and readable syntax: Using indentation instead of braces to define blocks
- Dynamic typing: No need to define variable types (though Type Hints are available)
- Automatic memory management: Built-in Garbage Collection
- Rich standard library: More than 200 built-in modules
- Multi-paradigm: Support for OOP, Functional, and Procedural
- Embeddable: C/C++ code can be used in Python
Main Applications
| Domain |
Key Libraries |
Example |
| Web Development |
Django, Flask, FastAPI |
Instagram, Pinterest |
| Data Science |
Pandas, NumPy, Matplotlib |
Financial data analysis |
| Artificial Intelligence |
TensorFlow, PyTorch, scikit-learn |
ChatGPT, AlphaGo |
| Automation |
Selenium, Requests, BeautifulSoup |
Web scraping, Telegram bot |
| DevOps |
Ansible, Docker SDK |
Infrastructure management |
Note: Python 3.x is the current version. Python 2 reached end-of-life in January 2020. Always use Python 3.8+.
2 Installation and Setup
Python on Windows, Linux and macOS
Installation on Linux (Recommended)
Ubuntu/Debian.
sudo apt install python3 python3-pip python3-venv
Fedora/RHEL.
sudo dnf install python3 python3-pip
Arch Linux.
sudo pacman -S python python-pip
Check.
Creating a Virtual Environment
Create virtual environment.
python3 -m venv myproject_env
Activation (Linux/macOS)
source myproject_env/bin/activate
Activation (Windows)
myproject_env\Scripts\activate
Install packages.
pip install numpy pandas requests
Save dependencies.
pip freeze > requirements.txt
Install from file.
pip install -r requirements.txt
Deactivation.
Why virtual environment? Every Python project should have its own virtual environment to prevent conflicts between different library versions. This is one of the best practices for Python development.
3 Basic Syntax
Basic rules for writing Python code
First Program
This is a single-line comment.
"""This is a
multi-line comment"""
Print multiple values.
print("Name:", "", "Age:", 25)
Using sep and end.
print("A", "B", "C", sep="-", end="!\n")
Output: A-B-C!
Indentation
In Python, indentation is mandatory and replaces braces {}. The standard is to use 4 spaces (never Tab!).
if True:
print("This is correct")
if True:
print("Nested indentation")
print("Outside condition")
Warning: Never mix Tab and Space! This is one of the most common beginner mistakes. In VS Code, configure Tab to convert to 4 spaces.
4 Variables and Data Types
Storing and managing data in Python
Basic Data Types
| Type |
Example |
Description |
Mutable? |
int |
42, -7, 1_000_000 |
Integers (unlimited size) |
❌ |
float |
3.14, -0.5, 2.5e10 |
Floating point numbers (IEEE 754 double-precision) |
❌ |
complex |
3+4j |
Complex numbers |
❌ |
str |
"hello", 'hi' |
Unicode strings |
❌ |
bool |
True, False |
Boolean value |
❌ |
NoneType |
None |
No value (null) |
❌ |
Variable Definition
Simple definition.
Multiple variables at once.
Swapping values.
Unpacking.
Using _ for values we don't need.
first, _, third = (1, 2, 3)
Extended unpacking (Python 3+)
first, *rest = [1, 2, 3, 4, 5]
First=1, rest=[2,3,4,5].
Type checking.
print(isinstance(age, int))
True.
Type Hints
from typing import List, Dict, Optional, Union
Function with Type Hints.
def greet(name: str, age: int) -> str:
return f"Hello {name}And {age} "
Optional - Value can be None.
def find_user(user_id: int) -> Optional[Dict[str, str]]:
pass
Union - Multiple possible types.
def process(data: Union[str, int]) -> str:
return str(data)
List Type.
numbers: List[int] = [1, 2, 3]
5 Operators
All operators available in Python
Arithmetic Operators
| Operator |
Name |
Example |
Result |
+ | Addition | 5 + 3 | 8 |
- | Subtraction | 5 - 3 | 2 |
* | Multiplication | 5 * 3 | 15 |
/ | Division | 5 / 2 | 2.5 |
// | Floor Division | 5 // 2 | 2 |
% | Modulus | 5 % 2 | 1 |
** | Power | 2 ** 3 | 8 |
Comparison Operators
True - Greater than or equal.
True - Less than or equal.
Is - Check Andor Object.
print(a is not [1, 2, 3])
True.
In - Check Membership.
Logical Operators
age = 25
has_license = True
And - And True With.
if age >= 18 and has_license:
print("You can drive")
Or - At least one is True With.
if age < 13 or age > 65:
print("You have a discount")
Not - Reverse.
if not has_license:
print("First get a license")
Short-circuit evaluation.
result = 0 or "default"
result = 5 and "value"
result = not 0
Bitwise Operators
Assignment Operators
| Operator |
Equivalent to |
Description |
+= | x = x + y | Add and assign |
-= | x = x - y | Subtract and assign |
*= | x = x * y | Multiply and assign |
/= | x = x / y | Divide and assign |
//= | x = x // y | Floor divide and assign |
%= | x = x % y | Modulus and assign |
**= | x = x ** y | Power and assign |
&= | x = x & y | Bitwise AND and assign |
|= | x = x | y | Bitwise OR and assign |
^= | x = x ^ y | Bitwise XOR and assign |
<<= | x = x << y | Shift left and assign |
>>= | x = x >> y | Shift right and assign |
:= | walrus | Assignment expression (Python 3.8+) |
Walrus operator (:=); Assignment inside condition.
if (n := len([1, 2, 3])) > 2:
print(f"List {n} ")
Usage in while.
while (line := input("Enter: ")) != "quit":
print(f"You entered: {line}")
6 Conditional if-else
Controlling program flow with conditions
Basic Structure
if age < 13:
print("Child")
elif age < 20:
print("Teenager")
elif age < 65:
print("Adult")
else:
print("Senior")
One-line condition (Ternary).
status = "Adult" if age >= 18 else "Minor"
Nested conditions.
if age >= 18:
if has_license:
print("You can drive")
else:
print("First get a license")
else:
print("You are still too young")
Truthy and Falsy Values
In Python, the following values are considered False:
False
None
0 (
0.0 (Float
"" (Empty
[] (List
{} (Dictionary
set() (Set
() (Tuple
All other values are True.
Using truthiness.
if name:
print(f"Hello {name}")
if items:
print(f"{len(items)} items available")
Check None.
result = None
if result is None:
print("No result found")
From any all.
numbers = [1, 2, 3, 0]
print(any(numbers))
print(all(numbers))
7 Loops
Repeating operations with for and while
for Loop
Loop over list.
fruits = ["", "And", ""]
for fruit in fruits:
print(fruit)
Using range.
for i in range(5):
print(i)
for i in range(2, 8):
print(i)
for i in range(0, 10, 2):
print(i)
Enumerate - Index and value simultaneously.
for index, fruit in enumerate(fruits):
print(f"{index}: {fruit}")
Zip - Combine multiple iterables.
names = ["", ""]
ages = [25, 30]
for name, age in zip(names, ages):
print(f"{name} {age} ")
Reversed.
for i in reversed(range(5)):
print(i)
Sorted.
numbers = [3, 1, 4, 1, 5]
for num in sorted(numbers):
print(num)
while Loop
count = 0
while count < 5:
print(count)
count += 1
While else.
while count < 10:
print(count)
count += 1
else:
print("Loop completed")
Infinite loop with exit condition.
while True:
user_input = input("Enter a number (q to exit): ")
if user_input == "q":
break
try:
number = int(user_input)
print(f"Square: {number ** 2}")
except ValueError:
print("Please enter a valid number")
break, continue, pass
Break - Exit loop.
for i in range(10):
if i == 5:
break
print(i)
Continue - Skip current iteration.
for i in range(10):
if i % 2 == 0:
continue
print(i)
Pass - Does nothing (placeholder).
Else for Loop.
for i in range(5):
if i == 10:
break
else:
print("No break occurred")
8 Comprehension
Building data structures concisely and efficiently
List Comprehension
Traditional method.
squares = []
for x in range(10):
squares.append(x ** 2)
List Comprehension.
squares = [x ** 2 for x in range(10)]
With condition.
evens = [x for x in range(20) if x % 2 == 0]
With if-else.
labels = ["even" if x % 2 == 0 else "odd" for x in range(10)]
Nested.
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
Flatten list.
nested = [[1, 2], [3, 4], [5, 6]]
flat = [x for sublist in nested for x in sublist]
Dictionary Comprehension
Build dictionary.
name_lengths = {name: len(name) for name in names}
With condition.
long_names = {name: len(name) for name in names if len(name) > 3}
Reverse dictionary.
original = {"a": 1, "b": 2, "c": 3}
reversed_dict = {v: k for k, v in original.items()}
Set Comprehension
numbers = [1, 2, 2, 3, 3, 3, 4]
Set Comprehension.
unique_squares = {x ** 2 for x in numbers}
Generator Expression.
sum_of_squares = sum(x ** 2 for x in range(1000000))
Warning: Do not overuse nested comprehensions. If code loses readability, use regular loops.
9 Match-Case (Python 3.10+)
Pattern Matching in Python
Simple Pattern Matching.
def http_status(status):
match status:
case 200:
return "OK"
case 404:
return "Not Found"
case 500:
return "Server Error"
case _:
return "Unknown status"
Pattern Matching with OR.
match status:
case 200 | 201:
print("Success")
case 400 | 401 | 403 | 404:
print("Client Error")
Pattern Matching on Lists.
match point:
case [0, 0]:
print("Origin")
case [x, 0]:
print(f"on x-axis at {x}")
case [0, y]:
print(f"on y-axis at {y}")
case [x, y]:
print(f"point ({x}, {y})")
case _:
print("Invalid format")
Pattern Matching on Dictionaries.
match user:
case {"name": str(name), "age": int(age)} if age >= 18:
print(f"{name} is an adult")
case {"name": name}:
print(f"Name: {name}")
case _:
print("Invalid data")
10 Lists
Sequential and mutable data structure
Creation and Access
List.
fruits = ["", "And", "", "And"]
mixed = [1, "hello", 3.14, True]
And.
Access Elements.
["", "And"] - From beginning.
["", ""] - Every 2nd one.
Check existence.
print(fruits.index("And"))
1.
List Methods
| Method |
Description |
Example |
append(x) | Add to end | lst.append(5) |
extend(iter) | Add multiple elements | lst.extend([1,2]) |
insert(i, x) | Insert at position i | lst.insert(0, "first") |
remove(x) | Remove first x | lst.remove("apple") |
pop([i]) | Remove and return | lst.pop() or lst.pop(0) |
clear() | Clear list | lst.clear() |
index(x) | Position of first x | lst.index("a") |
count(x) | Count of x | lst.count(1) |
sort() | Sort in-place | lst.sort(reverse=True) |
reverse() | Reverse in-place | lst.reverse() |
copy() | Shallow copy | new = lst.copy() |
numbers = [3, 1, 4, 1, 5, 9, 2, 6]
Sorting.
numbers.sort()
numbers.sort(reverse=True)
Sorting with key.
words = ["banana", "pie", "Washington"]
words.sort(key=len)
Sorted() - new list.
new_sorted = sorted(numbers, reverse=True)
Copying; Shallow copy.
shallow = numbers.copy()
shallow = numbers[:]
shallow = list(numbers)
Deep copy (for nested lists).
import copy
nested = [[1, 2], [3, 4]]
deep = copy.deepcopy(nested)
Note: sort() sorts the list in-place and None sorted() creates a new list.
11 Tuples
Sequential and immutable data structure
Tuple.
point = (3, 4)
single = (5,)
empty = ()
coords = 1, 2, 3
Access similar to list.
print(point[0])
print(point[-1])
print(point[1:])
Unpacking.
x, y = point
a, b, c = coords
Tuple as dictionary key.
locations = {
(0, 0): "Origin",
(1, 0): "East",
(0, 1): "North"
}
Named Tuple.
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)
print(p[0])
Nested tuples.
matrix = (
(1, 2, 3),
(4, 5, 6),
(7, 8, 9)
)
print(matrix[1][1])
When to use tuples: When data is fixed (like coordinates, configuration settings), or you want to prevent accidental changes, or need a hashable type (dictionary key, set member).
12 Dictionaries
Key-value data structure
Creation and Access
Dictionary.
person = {
"name": "",
"age": 25,
"city": ""
}
Or dict().
person2 = dict(name="", age=30)
Access.
print(person["name"])
print(person.get("age"))
print(person.get("job", "N/A"))
/And.
person["email"] = "ali@example.com"
person.update({"phone": "0912...", "age": 26})
Delete.
del person["city"]
email = person.pop("email")
last = person.popitem()
Check.
print("name" in person)
print(len(person))
Iteration.
for key in person:
print(key)
for key, value in person.items():
print(f"{key}: {value}")
for value in person.values():
print(value)
Dictionary Methods
| Method |
Description |
keys() | Return keys |
values() | Return values |
items() | Return (key, value) tuples |
get(key, default) | Safe access with default value |
setdefault(key, default) | Set if not exists |
update(other) | Update with another dictionary |
pop(key) | Remove key and return value |
popitem() | Remove and return last item |
clear() | Clear |
copy() | Shallow copy |
fromkeys(keys, value) | Build from list of keys |
Advanced dictionaries
Defaultdict.
from collections import defaultdict
word_count = defaultdict(int)
for word in ["apple", "banana", "apple"]:
word_count[word] += 1
print(word_count)
Counter.
from collections import Counter
counts = Counter(["a", "b", "a", "c", "a"])
print(counts.most_common(2))
OrderedDict (Python 3.7+ dictordered Is.
from collections import OrderedDict
Merge dictionaries (Python 3.9+).
dict1 = {"a": 1, "b": 2}
dict2 = {"b": 3, "c": 4}
merged = dict1 | dict2
Dictionary unpacking.
combined = {**dict1, **dict2}
Setdefault; Grouping with dictionary.
groups = {}
for item in [("A", 1), ("B", 2), ("A", 3)]:
key, value = item
groups.setdefault(key, []).append(value)
print(groups)
13 Sets
Unordered data structure with unique members
Set.
numbers = set([1, 2, 2, 3, 3, 3])
{1, 2, 3}.
{} empty dictionary makesFrom!
Set Operations.
Union.
Subscription.
Difference.
Symmetric Difference.
print(a.symmetric_difference(b))
Subset and Superset.
/Delete.
Frozenset - Immutable.
immutable = frozenset([1, 2, 3])
Can be dictionary key or set member.
Practical use: Sets are very efficient for removing duplicates, membership testing (O(1)), and mathematical set operations.
14 Strings
Working with text in Python
F-string (And Python 3.6+)
message = f"Hello {name}And {age} "
Formatting Advanced.
print(f"Pi = {pi:10.4f}")
3.1416.
print(f"Number = {42:05d}")
00042.
print(f"Binary = {42:b}")
101010.
Strings.
text = """This is a
multi-line string"""
Raw string.
path = r"C:\Users\Name\file.txt"
Bytes.
utf8 = "Hello".encode("utf-8")
text2 = utf8.decode("utf-8")
String Methods
| Method |
Description |
Example |
upper() | Uppercase | "hello".upper() → "HELLO" |
lower() | Lowercase | "HELLO".lower() → "hello" |
title() | First letter of each word | "hello world".title() → "Hello World" |
strip() | Remove whitespace | " hello ".strip() → "hello" |
split(sep) | Division | "a,b,c".split(",") → ["a","b","c"] |
join(iter) | Join | ",".join(["a","b"]) → "a,b" |
replace(old, new) | Replacement | "hello".replace("l","x") → "hexxo" |
find(sub) | Substring position | "hello".find("l") → 2 |
startswith() | Starts with | "hello".startswith("he") → True |
endswith() | Ends with | "hello.txt".endswith(".txt") → True |
isdigit() | Is it a number? | "123".isdigit() → True |
isalpha() | Is it a letter? | "abc".isalpha() → True |
count(sub) | Count occurrences | "hello".count("l") → 2 |
String Slicing.
Content Check.
And.
print(text.index("World"))
7.
Alignment.
print("hello".center(20, "-"))
"-------hello--------".
Partition.
print("key=value".partition("="))
('key', '=', 'value')
15 Functions
Defining and using functions in Python
Function Definition
Simple.
def greet(name):
"""Greeting the user."""
return f"Hello {name}!"
Function with default value.
def power(base, exponent=2):
return base ** exponent
print(power(3))
print(power(2, 3))
Args and kwargs.
def flexible(*args, **kwargs):
print("Positional:", args)
print("Keyword:", kwargs)
flexible(1, 2, 3, name="", age=25)
Unpacking during call.
nums = [1, 2]
power(*nums)
params = {"base": 2, "exponent": 3}
power(**params)
Keyword-only arguments (Python 3+).
def safe_divide(a, b, *, strict=False):
if strict and b == 0:
raise ValueError("Division by zero")
return a / b if b != 0 else None
safe_divide(10, 2, strict=True)
Positional-only (Python 3.8+).
def greet_pos(name, /, greeting="Hello"):
return f"{greeting} {name}"
greet_pos("")
greet_pos("", greeting="And")
Docstrings
def calculate_area(length, width):
"""Calculate rectangle area.
Args:
length (float): Rectangle length.
width (float): Rectangle width.
Returns:
float: Calculated area.
Raises:
ValueError: If length or width is negative.
Examples:
>>> calculate_area(5, 3)
15.0
"""
if length < 0 or width < 0:
raise ValueError("Dimensions cannot be negative")
return length * width
Access docstring.
print(calculate_area.__doc__)
print(calculate_area.__name__)
16 Lambda
Anonymous one-line functions
Definition lambda.
square = lambda x: x ** 2
print(square(5))
Argument.
add = lambda x, y: x + y
print(add(3, 4))
With map.
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
With filter.
evens = list(filter(lambda x: x % 2 == 0, numbers))
With sorted.
words = ["banana", "pie", "Washington"]
sorted_by_length = sorted(words, key=lambda x: len(x))
With reduce.
from functools import reduce
product = reduce(lambda x, y: x * y, [1, 2, 3, 4])
Closure lambda.
def make_multiplier(n):
return lambda x: x * n
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5))
print(triple(5))
Warning: Do not use lambda for complex operations. If lambda needs more than one expression, use def. PEP 8 recommends not assigning lambdas to variables.
17 Decorators
Changing function behavior without modifying code
Basic Decorator
Decorator.
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before function")
result = func(*args, **kwargs)
print("After function")
return result
return wrapper
@my_decorator
def say_hello():
print("Hello!")
Decorator with argument.
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"Hello {name}")
Functools.wraps - preserve metadata.
from functools import wraps
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
import time
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} {elapsed:.4f} Seconds")
return result
return wrapper
@timer
def slow_function():
"""This function is slow."""
import time
time.sleep(1)
slow_function()
print(slow_function.__name__)
print(slow_function.__doc__)
Class Decorators
@property.
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value < 0:
raise ValueError("Radius cannot be negative")
self._radius = value
@property
def area(self):
import math
return math.pi * self._radius ** 2
c = Circle(5)
print(c.radius)
c.radius = 10
print(c.area)
@staticmethod @classmethod.
class DateUtil:
@staticmethod
def is_leap_year(year):
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
@classmethod
def from_string(cls, date_str):
year, month, day = map(int, date_str.split("-"))
return cls(year, month, day)
print(DateUtil.is_leap_year(2024))
18 Modules and Packages
Organizing code with modules
Import.
import math
print(math.pi)
print(math.sqrt(16))
From import.
from math import pi, sqrt
print(pi)
Alias.
import numpy as np
import pandas as pd
Import all (not recommendedAnd).
Import Package; from . import module; from .. import parent_module.
__all__ - import *; File __init__.py:; __all__ = ['func1', 'func2', 'Class1'].
If __name__ == "__main__":; Code that only runs when file is executed directly.
if __name__ == "__main__":
print("This file was executed directly")
Sys.path - import.
import sys
print(sys.path)
Add custom path; sys.path.append("/path/to/modules").
Importlib - Dynamic import.
import importlib
module = importlib.import_module("math")
print(module.pi)
Package Structure
mypackage/
__init__.py
module1.py
module2.py
subpackage/
__init__.py
module3.py
Usage:; import mypackage.module1; from mypackage.subpackage import module3.
19 Generators
Generating values lazily
Generator Function.
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
counter = count_up_to(5)
print(next(counter))
print(next(counter))
Generator Expression.
squares = (x ** 2 for x in range(1000000))
Fibonacci Generator.
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
for num in fibonacci(10):
print(num)
Generator with send().
def running_average():
total = 0
count = 0
average = None
while True:
term = yield average
total += term
count += 1
average = total / count
avg = running_average()
next(avg)
print(avg.send(10))
print(avg.send(20))
print(avg.send(30))
Itertools.
Count - infinite counting.
for i in itertools.count(10, 2):
if i > 20: break
print(i)
Cycle - infinite cycling.
counter = 0
for item in itertools.cycle(["A", "B", "C"]):
if counter >= 6: break
print(item)
counter += 1
Chain - Join iterator.
for x in itertools.chain([1, 2], [3, 4], [5, 6]):
print(x)
20 Classes and Objects
Fundamentals of object-oriented programming in Python
Class Definition
class Dog:
species = "Canis familiaris"
def __init__(self, name, age):
self.name = name
self.age = age
def description(self):
return f"{self.name} is {self.age} years old"
def speak(self, sound):
return f"{self.name} says {sound}"
def __str__(self):
return f"Dog(name={self.name}, age={self.age})"
def __repr__(self):
return f"Dog('{self.name}', {self.age})"
Object.
buddy = Dog("Buddy", 3)
print(buddy.name)
print(buddy.species)
print(buddy.description())
print(buddy.speak("Woof Woof"))
print(str(buddy))
print(repr(buddy))
Type checking.
print(isinstance(buddy, Dog))
print(type(buddy))
Class Methods and Static Methods
class Person:
population = 0
def __init__(self, name):
self.name = name
Person.population += 1
@classmethod
def get_population(cls):
return cls.population
@classmethod
def create_anonymous(cls):
return cls("Anonymous")
@staticmethod
def is_adult(age):
return age >= 18
p1 = Person("")
p2 = Person("")
print(Person.get_population())
print(Person.is_adult(20))
anon = Person.create_anonymous()
print(anon.name)
21 Inheritance
Inheriting from other classes
Base Class.
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement")
def introduce(self):
return f"I am {self.name}"
Child Class.
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
self.breed = breed
def speak(self):
return f"{self.name} says Woof!"
def fetch(self):
return f"{self.name} is fetching"
Inheritance.
class FlyingDog(Dog):
def fly(self):
return f"{self.name} is flying!"
dog = Dog("Buddy", "Golden Retriever")
print(dog.introduce())
print(dog.speak())
print(dog.fetch())
MRO - Method Resolution Order.
Isinstance issubclass.
print(isinstance(dog, Dog))
print(isinstance(dog, Animal))
print(issubclass(Dog, Animal))
Multiple Inheritance
class Flyable:
def fly(self):
return "Flying!"
class Swimmable:
def swim(self):
return "Swimming!"
class Duck(Flyable, Swimmable):
def quack(self):
return "Quack!"
duck = Duck()
print(duck.fly())
print(duck.swim())
print(duck.quack())
MRO Inheritance.
22 Encapsulation
Hiding data and controlling access
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.__balance = balance
self._transaction_count = 0
@property
def balance(self):
return self.__balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("Balance cannot be negative")
self.__balance = value
def deposit(self, amount):
if amount <= 0:
raise ValueError("Amount must be positive")
self.__balance += amount
self._transaction_count += 1
def withdraw(self, amount):
if amount > self.__balance:
raise ValueError("Insufficient funds")
self.__balance -= amount
self._transaction_count += 1
account = BankAccount("", 1000)
account.deposit(500)
account.withdraw(200)
print(account.balance)
Name Mangling; __balance -> _BankAccount__balance; print(account.__balance) # Error!
print(account._BankAccount__balance)
__slots__ - And attributes.
class Point:
__slots__ = ["x", "y"]
def __init__(self, x, y):
self.x = x
self.y = y
23 Polymorphism
One interface, different implementations
Duck Typing.
class Duck:
def speak(self):
return "Quack!"
def move(self):
return "Swimming"
class Dog:
def speak(self):
return "Woof!"
def move(self):
return "Running"
def animal_concert(animals):
for animal in animals:
print(animal.speak())
animal_concert([Duck(), Dog()])
Abstract Base Classes.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
@abstractmethod
def perimeter(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
def perimeter(self):
return 2 * (self.width + self.height)
Shape = Shape() # Error! Power ABC instantiate.
rect = Rectangle(5, 3)
print(rect.area())
print(rect.perimeter())
Protocols (Python 3.8+).
from typing import Protocol
class Drawable(Protocol):
def draw(self) -> None:
...
class Circle:
def draw(self):
print("Drawing circle")
def render(item: Drawable):
item.draw()
24 Magic Methods
Double underscore methods (__dunder__)
| Method |
Operator/Function |
Description |
__init__(self) | Constructor | initialization |
__new__(cls) | Before __init__ | Create |
__del__(self) | Delete | destructor |
__str__(self) | str() | User-friendly display |
__repr__(self) | repr() | Detailed display |
__eq__(self, other) | == | Equality |
__ne__(self, other) | != | Inequality |
__lt__(self, other) | < | Less than |
__le__(self, other) | <= | Less than or equal |
__gt__(self, other) | > | Greater than |
__ge__(self, other) | >= | Greater than or equal |
__add__(self, other) | + | Addition |
__sub__(self, other) | - | Subtraction |
__mul__(self, other) | * | Multiplication |
__truediv__(self, other) | / | Division |
__len__(self) | len() | Length |
__getitem__(self, key) | obj[key] | Access |
__setitem__(self, key, val) | obj[key] = val | Set |
__contains__(self, item) | in | Membership |
__iter__(self) | iter() | iteration |
__next__(self) | next() | Next iteration |
__call__(self) | obj() | Call |
__enter__ / __exit__ | with | Context Manager |
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Vector({self.x}, {self.y})"
def __repr__(self):
return f"Vector({self.x!r}, {self.y!r})"
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __sub__(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 __rmul__(self, scalar):
return self * scalar
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __len__(self):
return int((self.x ** 2 + self.y ** 2) ** 0.5)
def __getitem__(self, index):
if index == 0: return self.x
if index == 1: return self.y
raise IndexError("Index out of range")
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)
print(v2 - v1)
print(v1 * 3)
print(3 * v1)
print(v1 == v2)
print(len(v1))
print(v1[0])
25 Read and write File
Working with files in Python
File Modes
| Mode |
Description |
'r' | Read (default) |
'w' | Write (overwrite) |
'x' | Exclusive creation (error if exists) |
'a' | Append to end |
'b' | Binary |
't' | Text (default) |
'+' | Read and write |
Reading entire file.
with open("data.txt", "r", encoding="utf-8") as f:
content = f.read()
print(content)
Reading line by line.
with open("data.txt", "r", encoding="utf-8") as f:
for line in f:
print(line.strip())
Reading to list.
with open("data.txt", "r", encoding="utf-8") as f:
lines = f.readlines()
Reading part of a file.
with open("data.txt", "r", encoding="utf-8") as f:
chunk = f.read(1024)
File.
with open("output.txt", "w", encoding="utf-8") as f:
f.write("Hello or!\n")
f.write("Second line\n")
Appending to file.
with open("log.txt", "a", encoding="utf-8") as f:
f.write("or\n")
Writing list of lines.
lines = ["Line 1", "Line 2", "Line 3"]
with open("lines.txt", "w", encoding="utf-8") as f:
f.writelines(line + "\n" for line in lines)
File Binary.
with open("image.png", "rb") as f:
data = f.read()
Seek tell.
with open("data.txt", "r", encoding="utf-8") as f:
f.seek(10)
pos = f.tell()
data = f.read(20)
26 Context Managers
Managing resources with with
Context Manager with Class.
class DatabaseConnection:
def __init__(self, host):
self.host = host
self.connection = None
def __enter__(self):
print(f"Connecting to {self.host}...")
self.connection = "connected"
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print("Closing connection...")
self.connection = None
return False
def query(self, sql):
return f"Result of: {sql}"
with DatabaseConnection("localhost") as db:
print(db.query("SELECT * FROM users"))
With contextlib.
from contextlib import contextmanager
@contextmanager
def managed_resource(name):
print(f"Acquiring {name}")
resource = {"name": name}
try:
yield resource
finally:
print(f"Releasing {name}")
with managed_resource("database") as res:
print(f"Using {res['name']}")
Contextlib.suppress.
from contextlib import suppress
with suppress(FileNotFoundError):
with open("nonexistent.txt") as f:
print(f.read())
Contextlib.redirect_stdout.
from contextlib import redirect_stdout
import io
f = io.StringIO()
with redirect_stdout(f):
print("This goes to StringIO")
output = f.getvalue()
print(output)
27 CSV and JSON
Working with common data formats
CSV
Reading CSV.
with open("data.csv", "r", encoding="utf-8") as f:
reader = csv.reader(f)
header = next(reader)
for row in reader:
print(row)
Reading as Dictionary.
with open("data.csv", "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
print(row["name"], row["age"])
Writing CSV.
data = [
["name", "age", "city"],
["", "25", ""],
["", "30", ""]
]
with open("output.csv", "w", encoding="utf-8", newline="") as f:
writer = csv.writer(f)
writer.writerows(data)
Writing from Dictionary.
with open("output.csv", "w", encoding="utf-8", newline="") as f:
fieldnames = ["name", "age", "city"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
writer.writeheader()
writer.writerow({"name": "", "age": "25", "city": ""})
JSON
Convert to JSON (serialize).
data = {
"name": "",
"age": 25,
"skills": ["Python", "Linux"],
"active": True
}
json_str = json.dumps(data, ensure_ascii=False, indent=2)
print(json_str)
Save to file.
with open("data.json", "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
Reading JSON.
with open("data.json", "r", encoding="utf-8") as f:
loaded = json.load(f)
Parse from string.
parsed = json.loads('{"name": "test", "value": 42}')
Custom JSON Encoder.
class DateTimeEncoder(json.JSONEncoder):
def default(self, obj):
from datetime import datetime
if isinstance(obj, datetime):
return obj.isoformat()
return super().default(obj)
from datetime import datetime
data = {"time": datetime.now()}
json_str = json.dumps(data, cls=DateTimeEncoder)
28 Try-Except
Error handling in Python
Basic Structure.
try:
result = 10 / 0
except ZeroDivisionError:
print("Power Division ")
Multiple except.
try:
num = int("abc")
except ValueError:
print("Value Name")
except TypeError:
print("Type Name")
Catching exception object.
try:
open("nonexistent.txt")
except FileNotFoundError as e:
print(f"Error: {e}")
print(f"Filename: {e.filename}")
Multiple except.
try:
pass
except (ValueError, TypeError) as e:
print(f"Error: {e}")
Generic except (use with caution).
try:
risky_operation()
except Exception as e:
print(f"Unexpected error: {e}")
Else - if no exception occurs.
try:
result = 10 / 2
except ZeroDivisionError:
print("Division by zero")
else:
print(f"Result: {result}")
Finally - always executes.
try:
f = open("file.txt")
data = f.read()
except FileNotFoundError:
print("File not found")
finally:
print("Cleanup code here")
Raise - throw exception.
def validate_age(age):
if age < 0:
raise ValueError("Age cannot be negative")
if age > 150:
raise ValueError("Age seems unrealistic")
return age
Raise without argument - re-raise.
try:
pass
except ValueError:
print("Logging error...")
raise
29 Custom Exceptions
Creating your own exceptions
Simple Exception.
class ValidationError(Exception):
"""Raised when input validation fails."""
pass
Exception with custom message.
class InsufficientFundsError(Exception):
def __init__(self, balance, amount):
self.balance = balance
self.amount = amount
super().__init__(f"Insufficient funds: balance={balance}, requested={amount}")
Exception Hierarchy.
class AppError(Exception):
"""Base exception for our application."""
pass
class DatabaseError(AppError):
"""Database related errors."""
pass
class ConnectionError(DatabaseError):
"""Connection failed."""
pass
class QueryError(DatabaseError):
"""Query execution failed."""
pass
class BankAccount:
def __init__(self, balance):
self.balance = balance
def withdraw(self, amount):
if amount > self.balance:
raise InsufficientFundsError(self.balance, amount)
self.balance -= amount
return self.balance
Exception Groups (Python 3.11+).
def faulty():
raise ExceptionGroup("group", [
ValueError("invalid value"),
TypeError("invalid type"),
ValueError("another value error")
])
try:
faulty()
except* ValueError as eg:
for error in eg.exceptions:
print(f"ValueError: {error}")
except* TypeError as eg:
for error in eg.exceptions:
print(f"TypeError: {error}")
30 Finally and Else
Complementary parts of try-except
Complete try-except structure.
try:
print("Trying...")
result = 10 / 2
except ZeroDivisionError:
print("Caught ZeroDivisionError")
else:
print(f"Success! Result: {result}")
finally:
print("Always executes")
Else: only if no exception occurs; finally: always executes (even with return).
Finally with return.
def demo():
try:
return "try"
finally:
print("finally executes before return!")
Suppress context manager.
from contextlib import suppress
with suppress(FileNotFoundError):
open("missing.txt")
print("Program continues...")
Assert - for debugging; assert condition, message; If condition is False, AssertionError; Disabled in -O (optimized) mode.
x = 10
assert x > 0, "x must be positive"
31 Iterators
Iteration protocol in Python
Custom Iterator.
class Countdown:
def __init__(self, start):
self.start = start
def __iter__(self):
return self
def __next__(self):
if self.start <= 0:
raise StopIteration
self.start -= 1
return self.start + 1
for num in Countdown(5):
print(num)
Separate Iterator.
class Range:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return RangeIterator(self.start, self.end)
class RangeIterator:
def __init__(self, start, end):
self.current = start
self.end = end
def __iter__(self):
return self
def __next__(self):
if self.current >= self.end:
raise StopIteration
num = self.current
self.current += 1
return num
Reversible Iterator.
class ReversibleRange:
def __init__(self, start, end):
self.start = start
self.end = end
def __iter__(self):
return iter(range(self.start, self.end))
def __reversed__(self):
return iter(range(self.end - 1, self.start - 1, -1))
r = ReversibleRange(1, 5)
print(list(r))
print(list(reversed(r)))
32 Contextlib
Context Manager tools
from contextlib import contextmanager, suppress, redirect_stdout
import os
@contextmanager.
@contextmanager
def temporary_directory(path):
os.makedirs(path, exist_ok=True)
try:
yield path
finally:
os.rmdir(path)
with temporary_directory("/tmp/test_dir") as path:
print(f"Working in {path}")
Suppress.
with suppress(FileNotFoundError, PermissionError):
os.remove("nonexistent_file.txt")
Redirect_stdout.
import io
f = io.StringIO()
with redirect_stdout(f):
print("Hello")
print("World")
print(f.getvalue())
ExitStack.
from contextlib import ExitStack
with ExitStack() as stack:
files = [
stack.enter_context(open(fname))
for fname in ["a.txt", "b.txt", "c.txt"]
]
Contextlib.closing.
from contextlib import closing
from urllib.request import urlopen
with closing(urlopen("https://example.com")) as page:
for line in page:
print(line)
33 Metaclasses
Classes that create classes
Type(name, bases, namespace); type itself is a metaclass.
Custom Metaclass.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=SingletonMeta):
def __init__(self):
print("Initializing database...")
db1 = Database()
db2 = Database()
print(db1 is db2)
Metaclass for validation.
class InterfaceMeta(type):
def __new__(mcs, name, bases, namespace):
if "process" not in namespace:
raise TypeError(f"{name} must implement process()")
return super().__new__(mcs, name, bases, namespace)
class Processor(metaclass=InterfaceMeta):
def process(self, data):
return data.upper()
Class BadProcessor(metaclass=InterfaceMeta):; pass # TypeError!
__init_subclass__ (Python 3.6+).
class BasePlugin:
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
print(f"Plugin registered: {cls.__name__}")
class EmailPlugin(BasePlugin):
pass
34 Async/Await
Asynchronous programming in Python
Simple coroutine.
async def say_hello():
print("Hello")
await asyncio.sleep(1)
print("World")
Asyncio.run(say_hello()).
Coroutine.
async def task(name, delay):
print(f"Task {name} starting")
await asyncio.sleep(delay)
print(f"Task {name} done")
return f"Result {name}"
async def main():
results = await asyncio.gather(
task("A", 2),
task("B", 1),
task("C", 3)
)
print(results)
task1 = asyncio.create_task(task("D", 1))
task2 = asyncio.create_task(task("E", 1))
await task1
await task2
for coro in asyncio.as_completed([
task("F", 3),
task("G", 1),
task("H", 2)
]):
result = await coro
print(f"Completed: {result}")
Asyncio.run(main()).
Async Context Manager.
class AsyncDatabase:
async def __aenter__(self):
print("Connecting...")
await asyncio.sleep(0.1)
return self
async def __aexit__(self, exc_type, exc, tb):
print("Disconnecting...")
await asyncio.sleep(0.1)
async def query(self, sql):
await asyncio.sleep(0.1)
return ["result1", "result2"]
Async with AsyncDatabase() as db:; results = await db.query("SELECT * FROM users").
Async Iterator.
class AsyncRange:
def __init__(self, n):
self.n = n
self.i = 0
def __aiter__(self):
return self
async def __anext__(self):
if self.i >= self.n:
raise StopAsyncIteration
await asyncio.sleep(0.01)
value = self.i
self.i += 1
return value
Async for i in AsyncRange(5):; print(i).
35 Type Hints Advanced
Python type system
from typing import (
List, Dict, Set, Tuple, Optional, Union,
Callable, Iterable, Iterator, Generator,
Any, TypeVar, Generic, Protocol
)
Basic types.
def process(items: List[int]) -> Dict[str, int]:
return {str(item): item for item in items}
Optional.
def find(items: List[str], target: str) -> Optional[int]:
try:
return items.index(target)
except ValueError:
return None
Union.
def parse(value: Union[str, int]) -> int:
return int(value)
Python 3.10+ syntax; def parse(value: str | int) -> int:
Callable.
def apply(func: Callable[[int], bool], items: List[int]) -> List[int]:
return [x for x in items if func(x)]
TypeVar.
def first(items: List[T]) -> Optional[T]:
return items[0] if items else None
Generic.
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: List[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
Protocol.
class Drawable(Protocol):
def draw(self) -> None: ...
def render(item: Drawable) -> None:
item.draw()from typing import TypedDict
class Movie(TypedDict):
name: str
year: int
rating: float
movie: Movie = {"name": "Inception", "year": 2010, "rating": 8.8}
36 os and sys
Interacting with the operating system
Os - paths and files.
print(os.getcwd())
os.chdir("/tmp")
print(os.listdir("."))
/Delete And.
os.makedirs("path/to/dir", exist_ok=True)
os.rmdir("empty_dir")
path = os.path.join("folder", "file.txt")
print(os.path.exists(path))
print(os.path.isfile(path))
print(os.path.isdir(path))
print(os.path.basename(path))
print(os.path.dirname(path))
print(os.path.splitext(path))
Pathlib (recommended).
p = Path("/home/user/documents")
print(p.name)
print(p.parent)
print(p.suffix)
print(p.exists())
print(p.is_file())
print(p.is_dir())
new_path = p / "file.txt"
print(new_path)
Sys.
print(sys.argv)
print(sys.path)
print(sys.version)
print(sys.platform)
Exit program; sys.exit(0) # And; sys.exit(1) # Error.
Stdin, stdout, stderr; sys.stdin.read(); sys.stdout.write("Hello\n"); sys.stderr.write("Error!\n").
37 datetime
Working with dates and times
from datetime import datetime, date, time, timedelta, timezone
import time as time_module
Current date and time.
now = datetime.now()
print(now)
Manual creation.
d = date(2024, 1, 15)
t = time(10, 30, 0)
dt = datetime(2024, 1, 15, 10, 30, 0)
Formatting.
print(now.strftime("%Y-%m-%d %H:%M:%S"))
print(now.strftime("%A, %B %d, %Y"))
Parsing.
parsed = datetime.strptime("2024-01-15", "%Y-%m-%d")
Timedelta.
future = now + timedelta(days=7, hours=3)
past = now - timedelta(weeks=1)
diff = future - now
print(diff.days)
Timezone.
utc = datetime.now(timezone.utc)
print(utc)
Timezone.
from datetime import timedelta
iran_tz = timezone(timedelta(hours=3, minutes=30))
tehran_time = datetime.now(iran_tz)
Zoneinfo (Python 3.9+).
from zoneinfo import ZoneInfo
tehran = datetime.now(ZoneInfo("Asia/Tehran"))
Timestamp.
ts = now.timestamp()
print(ts)
Timestamp.
from_ts = datetime.fromtimestamp(ts)
Sleep; time_module.sleep(2) # 2 Seconds.
Performance counter.
start = time_module.perf_counter()
end = time_module.perf_counter()
print(f"Elapsed: {end - start:.4f}s")
38 Regular Expressions (Regex)
Pattern matching with the re module
Main methods.
text = "My email is test@example.com and phone is 0912-345-6789"
Search - First match.
match = re.search(r"\w+@\w+\.\w+", text)
if match:
print(match.group())
print(match.start())
print(match.end())
print(match.span())
Match - Match at beginning of string.
m = re.match(r"My", text)
m = re.match(r"email", text)
Findall - All matches.
emails = re.findall(r"\w+@\w+\.\w+", text)
Finditer - iterator of Match objects.
for match in re.finditer(r"\d+", text):
print(match.group())
Sub - Replacement.
new_text = re.sub(r"\d", "*", text)
new_text, count = re.subn(r"\d", "*", text)
Split - Split with regex.
parts = re.split(r"\s+and\s+", text)
Groups.
pattern = r"(\w+)@(\w+)\.(\w+)"
match = re.search(pattern, text)
print(match.group(0))
print(match.group(1))
print(match.group(2))
print(match.group(3))
Named groups.
pattern = r"(?P<user>\w+)@(?P<domain>\w+)\.(?P<tld>\w+)"
match = re.search(pattern, text)
print(match.group("user"))
print(match.groupdict())
Compile - for repeated use.
email_pattern = re.compile(r"\w+@\w+\.\w+")
matches = email_pattern.findall(text)
Flags.
re.search(r"hello", "HELLO", re.IGNORECASE)
re.search(r"^hello", "hello\nworld", re.MULTILINE)
re.search(r"hello.world", "hello\nworld", re.DOTALL)
Regex Character Table
| Pattern |
Description |
. | Any character (except newline) |
\d | Digit (0-9) |
\D | Non-digit |
\w | Word character (a-z, A-Z, 0-9, _) |
\W | Non-word |
\s | Whitespace |
\S | Non-whitespace |
^ | Start of string |
$ | End of string |
* | Zero or more |
+ | One or more |
? | Zero or one |
{n} | Exactly n times |
{n,m} | Between n and m times |
[] | Character class |
| | OR |
() | Group |
(?:) | Non-capturing group |
(?=) | positive lookahead |
(?!) | negative lookahead |
39 Collections
Specialized data structures
from collections import (
Counter, defaultdict, OrderedDict,
deque, namedtuple, ChainMap
)
Counter.
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
count = Counter(words)
print(count)
print(count.most_common(2))
print(count["apple"])
print(count["orange"])
Defaultdict.
d = defaultdict(list)
d["fruits"].append("apple")
d["fruits"].append("banana")
print(dict(d))
Deque - double-ended queue.
dq = deque([1, 2, 3])
dq.append(4)
dq.appendleft(0)
dq.pop()
dq.popleft()
dq.rotate(1)
print(dq)
Namedtuple.
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)
print(p._asdict())
ChainMap.
defaults = {"theme": "light", "lang": "en"}
user_prefs = {"theme": "dark"}
combined = ChainMap(user_prefs, defaults)
print(combined["theme"])
print(combined["lang"])
OrderedDict (Python 3.7+ dict is ordered).
od = OrderedDict()
od["a"] = 1
od["b"] = 2
od.move_to_end("a")
print(list(od.keys()))
40 Itertools
Iterator tools
Count - infinite counting.
for i in itertools.count(10, 2):
if i > 20: break
print(i)
Cycle - infinite cycling.
counter = 0
for item in itertools.cycle(["A", "B", "C"]):
if counter >= 6: break
print(item, end=" ")
counter += 1
Repeat.
print(list(itertools.repeat("A", 3)))
Chain.
print(list(itertools.chain([1, 2], [3, 4], [5, 6])))
Compress.
selectors = [True, False, True]
print(list(itertools.compress(["A", "B", "C"], selectors)))
Dropwhile / takewhile.
data = [1, 2, 3, 4, 5]
print(list(itertools.dropwhile(lambda x: x < 3, data)))
print(list(itertools.takewhile(lambda x: x < 3, data)))
Groupby.
data = ["apple", "apricot", "banana", "blueberry", "cherry"]
for letter, group in itertools.groupby(data, key=lambda x: x[0]):
print(f"{letter}: {list(group)}")
Permutations.
print(list(itertools.permutations([1, 2, 3], 2)))
Combinations.
print(list(itertools.combinations([1, 2, 3, 4], 2)))
Product - Cartesian product.
print(list(itertools.product([1, 2], ["A", "B"])))
Accumulate.
print(list(itertools.accumulate([1, 2, 3, 4, 5])))
print(list(itertools.accumulate([1, 2, 3, 4], lambda x, y: x * y)))
41 unittest
Unit testing with the unittest module
Function we are testing.
def add(a, b):
return a + b
def divide(a, b):
if b == 0:
raise ValueError("Cannot divide by zero")
return a / b
Test class.
class TestMathOperations(unittest.TestCase):
def setUp(self):
self.numbers = [1, 2, 3, 4, 5]
def tearDown(self):
pass
@classmethod
def setUpClass(cls):
print("Setting up test class...")
@classmethod
def tearDownClass(cls):
print("Tearing down test class...")
def test_add_positive_numbers(self):
self.assertEqual(add(2, 3), 5)
def test_add_negative_numbers(self):
self.assertEqual(add(-2, -3), -5)
def test_add_zero(self):
self.assertEqual(add(0, 5), 5)
def test_divide_by_zero(self):
with self.assertRaises(ValueError):
divide(10, 0)
def test_list_contains(self):
self.assertIn(3, self.numbers)
self.assertNotIn(10, self.numbers)
def test_is_instance(self):
self.assertIsInstance("hello", str)
self.assertTrue(5 > 3)
self.assertFalse(3 > 5)
def test_almost_equal(self):
self.assertAlmostEqual(0.1 + 0.2, 0.3, places=7)
If __name__ == "__main__":; unittest.main().
Command line; python -m unittest test_module; python -m unittest test_module.TestClass; python -m unittest test_module.TestClass.test_method; python -m unittest discover -s tests -p "test_*.py".
assert methods
| Method |
Check |
assertEqual(a, b) | a == b |
assertNotEqual(a, b) | a != b |
assertTrue(x) | bool(x) is True |
assertFalse(x) | bool(x) is False |
assertIs(a, b) | a is b |
assertIsNone(x) | x is None |
assertIn(a, b) | a in b |
assertIsInstance(a, b) | isinstance(a, b) |
assertRaises(exc) | exception |
assertAlmostEqual(a, b) | Approximately |
assertGreater(a, b) | a > b |
assertLess(a, b) | a < b |
assertRegex(s, r) | regex match |
42 pytest
Modern Python testing framework
Pip install pytest.
Simple test - no class!
def add(a, b):
return a + b
def test_add():
assert add(2, 3) == 5
def test_add_negative():
assert add(-1, -1) == -2
Fixture.
@pytest.fixture
def sample_data():
return [1, 2, 3, 4, 5]
def test_sum(sample_data):
assert sum(sample_data) == 15
Fixture with scope.
@pytest.fixture(scope="module")
def database():
db = create_db()
yield db
db.cleanup()
Parametrize.
@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
Skip.
@pytest.mark.skip(reason="not implemented yet")
def test_new_feature():
pass
Skipif.
@pytest.mark.skipif(sys.platform == "win32", reason="Windows not supported")
def test_unix_feature():
pass
Xfail.
@pytest.mark.xfail(reason="known bug")
def test_bug():
assert 1 / 0
Raises.
def test_raises():
with pytest.raises(ZeroDivisionError):
1 / 0
Approx.
def test_float():
assert 0.1 + 0.2 == pytest.approx(0.3)
Monkeypatch.
def test_get_data(monkeypatch):
def mock_get(url):
return {"data": "mocked"}
monkeypatch.setattr("requests.get", mock_get)
Capsys.
def test_output(capsys):
print("hello")
captured = capsys.readouterr()
assert captured.out == "hello\n"
Tmp_path.
def test_file(tmp_path):
file = tmp_path / "test.txt"
file.write_text("hello")
assert file.read_text() == "hello"
Running pytest:
pytest - Execution
pytest -v - verbose
pytest -k "test_name" - Filter by name
pytest -x - Stop on first error
pytest --tb=short - Short traceback
pytest -s - Show print statements
pytest --cov=module - coverage (requires pytest-cov)
43 Debugging Techniques
Python debugging tools and techniques
Pdb - Python Debugger.
def buggy_function(x):
y = x + 10
pdb.set_trace()
z = y * 2
return z
Python 3.7+ breakpoint(); breakpoint() # Equivalent to pdb.set_trace().
Pdb commands:; n (next) - Next line; s (step) - Step into function; c (continue) - Continue; q (quit) - Exit; p variable - Print variable; l (list) - Show code; b line (break) - breakpoint; r (return) - Until return.
Logging.
logging.basicConfig(
level=logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
logger.debug("Debug message")
logger.info("Info message")
logger.warning("Warning message")
logger.error("Error message")
logger.critical("Critical message")
Traceback.
try:
1 / 0
except Exception:
traceback.print_exc()
tb = traceback.format_exc()
print(tb)
Inspect.
def my_function(a, b, c=10):
pass
print(inspect.signature(my_function))
print(inspect.getsource(my_function))
Timeit.
print(timeit.timeit("sum(range(100))", number=10000))
print(timeit.repeat("sum(range(100))", repeat=3, number=10000))
CProfile.
import cProfile
import pstats
def slow_function():
return sum(i * i for i in range(100000))
profiler = cProfile.Profile()
profiler.enable()
slow_function()
profiler.disable()
stats = pstats.Stats(profiler)
stats.sort_stats("cumulative")
stats.print_stats(10)
44 Performance Tips
Optimizing Python code
1. Using built-ins; Bad.
result = []
for i in range(1000):
result.append(i * 2)
Good.
result = [2 * i for i in range(1000)]
result = list(map(lambda x: x * 2, range(1000)))
2. set/dict for search; Bad - O(n).
if item in list(items):
pass
Good - O(1).
item_set = set(items)
if item in item_set:
pass
3. join instead of + for strings; Bad.
result = ""
for s in strings:
result += s
Good.
result = "".join(strings)
4. Generator for large data; Bad - or.
result = [process(x) for x in huge_list]
Good -.
result = (process(x) for x in huge_list)
5. Local variables are faster; Bad.
def slow():
for i in range(1000000):
math.sqrt(i)
Good.
def fast():
sqrt = math.sqrt
for i in range(1000000):
sqrt(i)
6. __slots__ for memory reduction.
class Point:
__slots__ = ["x", "y"]
def __init__(self, x, y):
self.x = x
self.y = y
7. functools.lru_cache.
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
8. multiprocessing for CPU-bound.
from multiprocessing import Pool
def square(x):
return x * x
with Pool() as pool:
results = pool.map(square, range(1000))
9. threading for I/O-bound.
from concurrent.futures import ThreadPoolExecutor
def fetch_url(url):
import requests
return requests.get(url).status_code
urls = ["https://example.com", "https://google.com"]
with ThreadPoolExecutor(max_workers=5) as executor:
results = list(executor.map(fetch_url, urls))
45 Profiling
Analyzing code performance
CProfile.
import cProfile
import pstats
import io
def heavy_computation():
total = 0
for i in range(100000):
total += i ** 2
return total
pr = cProfile.Profile()
pr.enable()
heavy_computation()
pr.disable()
s = io.StringIO()
ps = pstats.Stats(pr, stream=s).sort_stats("cumulative")
ps.print_stats(10)
print(s.getvalue())
Line_profiler (pip install line_profiler); @profile; def my_function():; pass; kernprof -l -v script.py.
Memory_profiler (pip install memory_profiler); from memory_profiler import profile; @profile; def my_function():; pass; python -m memory_profiler script.py.
Timeit.
Single line.
print(timeit.timeit("[x**2 for x in range(1000)]", number=100))
Multiple lines.
setup = "import random; data = [random.random() for _ in range(1000)]"
stmt = "sorted(data)"
print(timeit.timeit(stmt, setup=setup, number=100))
Perf_counter.
start = time.perf_counter()
end = time.perf_counter()
print(f"Elapsed: {end - start:.6f} seconds")
Dis - bytecode.
def example():
x = [1, 2, 3]
return sum(x)
46 Memory Management
Understanding and optimizing memory usage
Sys.getsizeof.
print(sys.getsizeof(0))
print(sys.getsizeof(""))
print(sys.getsizeof([]))
print(sys.getsizeof({}))
Gc - garbage collector.
Enable/disable; gc.disable(); gc.enable().
Force collection.
Collectable objects.
Weakref - weak reference.
obj = MyClass()
ref = weakref.ref(obj)
print(ref())
WeakKeyDictionary / WeakValueDictionary; For cache without preventing garbage collection.
__del__ and destructor.
class Resource:
def __init__(self):
print("Resource acquired")
def __del__(self):
print("Resource released")
Context Manager better than __del__.
class BetterResource:
def __enter__(self):
print("Resource acquired")
return self
def __exit__(self, *args):
print("Resource released")
With BetterResource() as r:; pass.
Tracemalloc.
tracemalloc.start()
current, peak = tracemalloc.get_traced_memory()
print(f"Current: {current / 1024 / 1024:.2f} MB")
print(f"Peak: {peak / 1024 / 1024:.2f} MB")
tracemalloc.stop()