Python
Points to consider
- dynamically typed language means the type of a variable is determined at runtime, not when the code is written.
Python Data Structures: Operations & Methods
| Operation | String | List | Tuple | Dict |
|---|---|---|---|---|
| Create | s = "hello" π‘ |
l = [1,2,3] βοΈ |
t = (1,2,3) π‘ |
d = {"a":1,"b":2} βοΈ |
| Read | s[0] π‘ |
l[0] βοΈ |
t[0] π‘ |
d["a"] βοΈ |
| Update | β Immutable π‘ | l[0] = 10 βοΈ |
β Immutable π‘ | d["a"] = 10 βοΈ |
| Delete | β π‘ | del l[0] βοΈ |
β π‘ | del d["a"] βοΈ |
| Length | len(s) π‘ |
len(l) π‘ |
len(t) π‘ |
len(d) π‘ |
| Search | "he" in s π‘ |
2 in l π‘ |
2 in t π‘ |
"a" in d π‘ |
| Substring / Subsequence | s[1:4] π‘ |
l[1:4] π‘ |
t[1:4] π‘ |
β π‘ |
| Sort | sorted(s) π‘ |
l.sort() βοΈ / sorted(l) π‘ |
sorted(t) π‘ |
sorted(d) π‘ |
| Slice | s[1:3] π‘ |
l[1:3] π‘ |
t[1:3] π‘ |
β π‘ |
| Reverse | s[::-1] π‘ |
l.reverse() βοΈ / l[::-1] π‘ |
t[::-1] π‘ |
β π‘ |
| Methods | s.upper(), s.lower(), s.split(), s.replace("a","b") π‘ |
l.append(4), l.extend([5,6]), l.pop(), l.remove(2) βοΈ |
t.count(1), t.index(2) π‘ |
d.keys(), d.values(), d.items(), d.get("a") π‘ |
Legend
- π‘ Immutable / Returns New Object β The original object is not modified.
- βοΈ Mutable / In-place Modification β The original object changes.
π Python Loops & Conditional Statements Cheat Sheet
| Statement Type | Syntax / Example | Notes |
|---|---|---|
| If statement | if condition: # code |
Executes block if condition is True |
| If-Else statement | if condition: # code else: # code |
Executes else block if condition is False |
| If-Elif-Else statement | if condition1: # code elif condition2: # code else: # code |
Handles multiple conditions |
| Ternary / Conditional Expression | number = 5 x = 10 if number > 0 else 20 print(x) |
Single-line conditional assignment |
| For loop | for i in iterable: # code |
Iterates over sequence, list, string, tuple, dict keys, etc. |
| For loop with range | for i in range(5): # code |
Generates numbers 0β4 |
| While loop | while condition: # code |
Loops while condition is True |
| Break statement | for i in range(5): if i == 3: break |
Exits the nearest enclosing loop |
| Continue statement | for i in range(5): if i == 2: continue print(i) |
Skips current iteration |
| Pass statement | if condition: pass |
Placeholder; does nothing |
ποΈ File & Folder CRUD Operations
| Operation | File | Folder |
|---|---|---|
| Create | open("file.txt", "w") / open("file.txt", "x") |
os.mkdir("folder") os.makedirs("folder/subfolder") |
| Read | open("file.txt", "r").read() readline() / readlines() |
os.listdir("folder") |
| Update / Write | open("file.txt", "a").write("text") open("file.txt", "w").write("overwrite") |
β Direct update not applicable (use files inside folder) |
| Delete | os.remove("file.txt") |
os.rmdir("folder") (empty) shutil.rmtree("folder") (non-empty) |
| Check existence | os.path.exists("file.txt") |
os.path.exists("folder") |
| Rename / Move | os.rename("old.txt", "new.txt") |
os.rename("old_folder", "new_folder") shutil.move("folder", "new_path") |
Exception
1. Exception Handling Structure
Python uses try, except, else, and finally blocks.
try:
# Code that may raise an exception
result = 10 / 0
except ZeroDivisionError as e:
# Handle specific exception
print("Error: Division by zero!", e)
except Exception as e:
# Handle any other exception
print("Some error occurred:", e)
else:
# Executes if no exception occurs
print("Result is", result)
finally:
# Executes no matter what
print("Execution finished")
Key points:
try β risky code
except β handles exceptions
else β runs if no exception
finally β runs always (cleanup)
2. Throwing (Raising) an Exception
#Use raise to throw exceptions.
x = -5
if x < 0:
raise ValueError("x must be non-negative")
#Throws a ValueError with a custom message.
3. User-Defined Exception
You can create custom exceptions by inheriting from Exception:
##### Define custom exception
class MyCustomError(Exception):
def __init__(self, message):
super().__init__(message)
# Raise the custom exception
age = -1
if age < 0:
raise MyCustomError("Age cannot be negative!")
#Inherits from Exception (or a more specific subclass if desired).
#Can add custom attributes or methods for richer error info.
Exception hierarchy
The class hierarchy for built-in exceptions is:
# Python Exception Hierarchy: Catchable vs Non-Catchable
BaseException
βββ β BaseExceptionGroup
βββ β GeneratorExit
βββ β KeyboardInterrupt
βββ β SystemExit
βββ β
Exception
βββ β
ArithmeticError
β βββ β
FloatingPointError
β βββ β
OverflowError
β βββ β
ZeroDivisionError
βββ β
AssertionError
βββ β
AttributeError
βββ β
BufferError
βββ β
EOFError
βββ β
ExceptionGroup
βββ β
ImportError
β βββ β
ModuleNotFoundError
βββ β
LookupError
β βββ β
IndexError
β βββ β
KeyError
βββ β
MemoryError
βββ β
NameError
β βββ β
UnboundLocalError
βββ β
OSError
β βββ β
BlockingIOError
β βββ β
ChildProcessError
β βββ β
ConnectionError
β β βββ β
BrokenPipeError
β β βββ β
ConnectionAbortedError
β β βββ β
ConnectionRefusedError
β β βββ β
ConnectionResetError
β βββ β
FileExistsError
β βββ β
FileNotFoundError
β βββ β
InterruptedError
β βββ β
IsADirectoryError
β βββ β
NotADirectoryError
β βββ β
PermissionError
β βββ β
ProcessLookupError
β βββ β
TimeoutError
βββ β
ReferenceError
βββ β
RuntimeError
β βββ β
NotImplementedError
β βββ β
PythonFinalizationError
β βββ β
RecursionError
βββ β
StopAsyncIteration
βββ β
StopIteration
βββ β
SyntaxError
β βββ β
IndentationError
β βββ β
TabError
βββ β
SystemError
βββ β
TypeError
βββ β
ValueError
β βββ β
UnicodeError
β βββ β
UnicodeDecodeError
β βββ β
UnicodeEncodeError
β βββ β
UnicodeTranslateError
βββ β
Warning
βββ β
BytesWarning
βββ β
DeprecationWarning
βββ β
EncodingWarning
βββ β
FutureWarning
βββ β
ImportWarning
βββ β
PendingDeprecationWarning
βββ β
ResourceWarning
βββ β
RuntimeWarning
βββ β
SyntaxWarning
βββ β
UnicodeWarning
βββ β
UserWarning
β Legend:
-
β Catchable in try-except (subclasses of Exception)
-
β Non-catchable / System exit exceptions (directly under BaseException)
β Rule of thumb:
-
Catch Exception and its subclasses.
-
Avoid catching BaseException directly unless you specifically want to handle system exit or interrupts (rare).
Threads
Non-Daemon Thread Case:
The main thread waits for all non-daemon threads to finish before the program exits.
By default, all threads are non-daemon threads i
Daemon Thread Case:
The main thread does not wait for daemon threads to complete.
When the main thread finishes executing, the program exits.
All running daemon threads are automatically terminated at that time.
β In short:
Non-daemon thread: Program waits for completion.
Daemon thread: Program exits without waiting; daemon threads stop immediately.
Synchronizing Threads
simple thread
import threading # Import threading module to work with threads
def helloworld():
print("Hello World!") # Function that prints a message
t1 = threading.Thread(target=helloworld) # Create a thread that will execute helloworld()
t1.start() # Start the thread and run the function concurrently
thread join
import threading # Import threading module to create and manage threads
def hello():
for x in range(50): # Loop 50 times
print("Hello!") # Print Hello each time
t1 = threading.Thread(target=hello) # Create a thread that will run the hello() function
t1.start() # Start the thread execution
t1.join() # Wait for thread t1 to finish before continuing
print("Another Text") # This will print only after the thread finishes
Lock and Release
import threading
import time
x = 8192 # Shared variable that both threads will modify
lock = threading.Lock() # Lock to ensure only one thread accesses x at a time
def double():
global x, lock # Use the shared variable and lock
lock.acquire() # Acquire the lock before entering critical section
while x < 16384: # Keep doubling until x reaches 16384
x *= 2 # Double the value of x
print(x) # Print the updated value
time.sleep(1) # Pause for 1 second to simulate processing
print("Reached the maximum!") # Message when maximum limit is reached
lock.release() # Release the lock so other threads can use the resource
def halve():
global x, lock # Use the shared variable and lock
lock.acquire() # Acquire the lock before modifying x
while x > 1: # Keep halving until x becomes 1
x /= 2 # Divide the value of x by 2
print(x) # Print the updated value
time.sleep(1) # Pause for 1 second
print("Reached the minimum!") # Message when minimum limit is reached
# Note: lock.release() is missing here, so the lock may remain held
t1 = threading.Thread(target=halve) # Thread that halves the value
t2 = threading.Thread(target=double) # Thread that doubles the value
t1.start() # Start the halve thread
t2.start() # Start the double thread
semaphore
import threading
import time
semaphore = threading.BoundedSemaphore(value=5) # Allow maximum 5 threads at the same time
def access(thread_number):
print("{} is trying to access!".format(thread_number)) # Thread requests access
semaphore.acquire() # Acquire semaphore (wait if limit reached)
print("{} was granted access!".format(thread_number)) # Access granted
time.sleep(5) # Simulate using the shared resource for 5 seconds
print("{} is now releasing!".format(thread_number)) # Thread finished its work
semaphore.release() # Release semaphore so another thread can access
for thread_number in range(1, 11): # Create 10 threads
t = threading.Thread(target=access, args=(thread_number,)) # Create thread
t.start() # Start thread execution
time.sleep(1) # Start each thread with 1-second gap
Events
import threading # Import threading module
event = threading.Event() # Create an Event object used for thread synchronization
def myfunction():
print("Waiting for event to trigger...\n") # Message showing the thread is waiting
event.wait() # Block the thread until the event is triggered
print("Performing action XYZ now...") # Execute action after event is set
t1 = threading.Thread(target=myfunction) # Create a thread to run myfunction
t1.start() # Start the thread
x = input("Do you want to trigger the event? (y/n)") # Ask user whether to trigger the event
if x == "y":
event.set() # Trigger the event and wake up the waiting thread
Queue
The queue module in Python provides three thread-safe queue types: FIFO Queue, LIFO Queue, and Priority Queue.
FIFO
A standard queue.Queue in Python follows the FIFO (First-In, First-Out) principle. You can visualize it like a line at a grocery store: the first person to get in line is the first person to be served.
import queue
# Initialize a new First-In-First-Out (FIFO) queue object
q = queue.Queue()
# Define a list of integers to be processed
numbers = [10, 20, 30, 40, 50, 60, 70]
# Iterate through the list and add each integer to the back of the queue
for number in numbers:
q.put(number)
# Remove and return the first item that was put into the queue (10)
print(q.get())
LIFO
This script demonstrates a Last-In, First-Out behavior. Think of it like a stack of trays; the last one you put on top is the first one you pick up.
import queue
# Create a Last-In, First-Out (LIFO) queue, essentially a Stack
q = queue.LifoQueue()
# Define a list of numbers from 1 to 7
numbers = [1, 2, 3, 4, 5, 6, 7]
# Loop through the list and push each number onto the top of the stack
for x in numbers:
q.put(x)
# Since it is LIFO, get() removes and returns the most recently added item (7)
print(q.get())
Priority Queue
This script uses a Priority Queue, where items are retrieved based on their assigned priority value rather than the order they were added. In Python, the lowest value is given the highest priority.
import queue
# Create a Priority Queue
q = queue.PriorityQueue()
# Adding tuples: (priority_number, data)
# Note: Lower numbers are processed first
q.put((2, "Hello World!"))
q.put((11, 99))
q.put((5, 7.5))
q.put((1, True)) # This has the lowest value (1), so it will be first
# Continue looping as long as the queue is not empty
while not q.empty():
# Removes and prints items in ascending order of priority
print(q.get())
Socket
Basic Socket Server
This snippet sets up a simple TCP server that listens for incoming connections on the local machine and sends a welcome message to any client that connects.
import socket
# Create a new socket using IPv4 (AF_INET) and TCP (SOCK_STREAM)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Bind the socket to the local address 127.0.0.1 and port 55555
s.bind(('127.0.0.1', 55555))
# Put the server in listening mode to wait for clients
s.listen()
# Infinite loop to keep the server running and accepting connections
while True:
# Block and wait for a new connection; returns the client socket and address
client, address = s.accept()
# Print the address of the newly connected client to the console
print("Connected to {}".format(address))
# Send an encoded welcome message to the client
client.send("You are connected!".encode())
# Close the connection with the current client
client.close()
Basic Socket Client
Python script for a simple TCP client
import socket # Import the built-in library for network communication
# Create a socket object using IPv4 (AF_INET) and TCP (SOCK_STREAM)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to the server running on the local machine (127.0.0.1) at port 55555
s.connect(('127.0.0.1', 55555))
# Receive up to 1024 bytes of data from the server
message = s.recv(1024)
# Close the connection to free up the resource
s.close()
# Convert the received bytes into a readable string and print it
print(message.decode())
DB
SQLlite
Basic workflow for using the sqlite3 library in Python: connecting to a database, creating a table, inserting data, and querying that data.
import sqlite3
# Connect to the database file (creates it if it doesn't exist)
connection = sqlite3.connect('mydata.db')
# Create a cursor object to execute SQL commands
cursor = connection.cursor()
# Create a new table named 'persons' with three columns
cursor.execute("""
CREATE TABLE IF NOT EXISTS persons (
first_name TEXT,
last_name TEXT,
age INTEGER
)
""")
# Insert three records into the 'persons' table
cursor.execute("""
INSERT INTO persons VALUES
('Paul', 'Smith', 24),
('Mark', 'Johnson', 42),
('Anna', 'Smith', 34)
""")
# Query the database for all columns where the last name is 'Smith'
cursor.execute("""
SELECT * FROM persons
WHERE last_name = 'Smith'
""")
# Retrieve all results from the query and print them as a list of tuples
rows = cursor.fetchall()
print(rows)
# Save (commit) the changes to the database
connection.commit()
# Close the connection to the database file
connection.close()
Recursion
Search file in a dir and its sub dir
import os
def find_item(path, name):
# Get all items (files + directories) in the current path
items = os.listdir(path)
# First pass: check if the target exists directly in this directory
for item in items:
# Construct the full absolute path of the item
full_path = os.path.join(path, item)
# If the name matches the target file/folder name
if item == name:
# Return the full path immediately (stop searching)
return full_path
# Second pass: recursively search inside subdirectories
for item in items:
# Construct the full path again
full_path = os.path.join(path, item)
# If the item is a directory, search inside it
if os.path.isdir(full_path):
# Recursive call to search inside this directory
result = find_item(full_path, name)
# If the item was found in the recursive search
if result:
# Return the found path immediately
return result
# If the item was not found in this directory or any subdirectory
return None
# Example usage
result = find_item("/your/start/path", "target_name")
print(result)
XML Processing
Comparison of packages
| Feature | minidom | SAX | ElementTree | lxml |
|---|---|---|---|---|
| Read XML | β | β | β | β |
| Write XML | β | β | β | β |
| Modify (add/delete nodes) | β | β | β | β |
| XPath search | β | β | Limited | β |
| Schema validation (XSD) | β | β | β | β |
| Speed | Slow | Very fast | Fast | Very fast |
| Memory usage | High | Very low | Moderate | Moderate |
| Large XML support | Poor | Excellent | Good | Excellent |
| --- |
SAX (Simple API for XML)
SAX (Simple API for XML) is designed as an event-driven parser. It "streams" through an XML document from top to bottom, triggering "events" (like startElement or endElement) as it goes. Because it doesn't load the whole file into memory, it is incredibly fast and efficient for reading massive files.
import xml.sax
# Define a custom handler to manage XML events
class GroupHandler(xml.sax.ContentHandler):
def __init__(self):
# Initialize attributes to avoid AttributeErrors if a tag is missing
self.current = ""
self.name = ""
self.age = ""
self.weight = ""
self.height = ""
def startElement(self, name, attrs):
"""Called when an opening tag like <person id="123"> is found."""
self.current = name
if self.current == "person":
print("\n----- PERSON -----")
# Extract the 'id' attribute from the person tag
if 'id' in attrs:
print("ID: {}".format(attrs['id']))
def characters(self, content):
"""Called when text data inside a tag is processed."""
if self.current == "name":
self.name = content
elif self.current == "age":
self.age = content
elif self.current == "weight":
self.weight = content
elif self.current == "height":
self.height = content
def endElement(self, name):
"""Called when a closing tag like </name> is found."""
if name == "name":
print("Name: {}".format(self.name))
elif name == "age":
print("Age: {}".format(self.age))
elif name == "weight":
# Fixed typo: changed 'self.seig' from image to 'self.weight'
print("Weight: {}".format(self.weight))
elif name == "height":
# Fixed logic: changed second 'age' check from image to 'height'
print("Height: {}".format(self.height))
# Reset current tag to prevent processing whitespace between tags
self.current = ""
# --- Execution Logic ---
# 1. Create the handler instance
handler = GroupHandler()
# 2. Initialize the SAX parser
parser = xml.sax.make_parser()
# 3. Connect the handler to the parser
parser.setContentHandler(handler)
# 4. Feed the XML file into the parser
# Ensure 'data.xml' exists in your directory!
try:
parser.parse('data.xml')
except FileNotFoundError:
print("Error: 'data.xml' not found. Please ensure the file exists.")
Minimal DOM implementation
This code uses the xml.dom.minidom library in Python to read, display, and modify an XML file
import xml.dom.minidom
# Load and parse the XML file into a DOM tree
domtree = xml.dom.minidom.parse('data.xml')
# Get the root element of the document (the 'group' tag)
group = domtree.documentElement
# Extract all elements with the tag name 'person'
persons = group.getElementsByTagName('person')
# Iterate through each person and print their details
for person in persons:
print("-----PERSON-----")
# Check for and print the 'id' attribute if it exists
if person.hasAttribute('id'):
print("ID: {}".format(person.getAttribute('id')))
# Access child nodes to extract text data for Name, Age, Weight, and Height
print("Name: {}".format(person.getElementsByTagName('name')[0].childNodes[0].data))
print("Age: {}".format(person.getElementsByTagName('age')[0].childNodes[0].data))
print("Weight: {}".format(person.getElementsByTagName('weight')[0].childNodes[0].data))
print("Height: {}".format(person.getElementsByTagName('height')[0].childNodes[0].data))
# Create a new 'person' element and set its ID attribute
newperson = domtree.createElement('person')
newperson.setAttribute('id', '6')
# Create the 'name' element and add text to it
name = domtree.createElement('name')
name.appendChild(domtree.createTextNode('Paul Green'))
# Create the 'age' element and add text to it
age = domtree.createElement('age')
age.appendChild(domtree.createTextNode('19'))
# Create the 'weight' element and add text to it
weight = domtree.createElement('weight')
weight.appendChild(domtree.createTextNode('80'))
# Create the 'height' element and add text to it
height = domtree.createElement('height')
height.appendChild(domtree.createTextNode('179'))
# Append all new sub-elements to the new 'person' container
newperson.appendChild(name)
newperson.appendChild(age)
newperson.appendChild(weight)
newperson.appendChild(height)
# Append the new person to the root 'group' element
group.appendChild(newperson)
# Save the updated DOM tree back to the 'data.xml' file
domtree.writexml(open('data.xml', 'w'))
data.xml
<group>
<person id="1">
<name>Mike Smith</name>
<age>34</age>
<weight>90</weight>
<height>175</height>
</person>
<person id="2">
<name>Anna Smith</name>
<age>54</age>
<weight>91</weight>
<height>188</height>
</person>
<person id="3">
<name>Bob Johnson</name>
<age>25</age>
<weight>76</weight>
<height>190</height>
</person>
<person id="4">
<name>Sara Jones</name>
<age>56</age>
<weight>82</weight>
<height>170</height>
</person>
</group>
Logging
import logging
# Configures the root logger to output to the console at the DEBUG level
logging.basicConfig(level=logging.DEBUG)
# Creates a custom logger instance named "MyLogger"
logger = logging.getLogger("MyLogger")
# Sets the internal threshold for this logger to capture all messages (DEBUG and above)
logger.setLevel(logging.DEBUG)
# Creates a handler that writes log messages to a file named "mylog.log"
handler = logging.FileHandler("mylog.log")
# Sets the file handler to only record messages that are INFO or higher (ignoring DEBUG)
handler.setLevel(logging.INFO)
# Defines the visual format of the log: "LEVEL - TIMESTAMP: MESSAGE"
formatter = logging.Formatter("%(levelname)s - %(asctime)s: %(message)s")
# Applies the defined format to the file handler
handler.setFormatter(formatter)
# Attaches the file handler to our custom logger
logger.addHandler(handler)
# This will appear in the console (due to basicConfig) but NOT in the file (due to handler level)
logger.debug("This is a debug message!")
# This will appear in both the console and the "mylog.log" file
logger.info("This is important information!")
Magic / Dunder(Double Underscore) methods
class Vector:
# The constructor: Initializes new instances of the class
def __init__(self, x, y):
self.x = x
self.y = y
# Operator Overloading: Defines behavior for the '+' operator
def __add__(self, other):
# Returns a new Vector object with the sums of x and y
return Vector(self.x + other.x, self.y + other.y)
# String Representation: Defines how the object looks when printed or inspected
def __repr__(self):
return f"X: {self.x}, Y: {self.y}"
# Length: Defines what len(obj) returns (hardcoded to 10 here)
def __len__(self):
return 10
# Callable: Allows the object to be called like a function using ()
def __call__(self):
print("Called")
# --- Execution ---
v1 = Vector(10, 20)
v2 = Vector(50, 60)
# Triggers __add__: v1 is 'self', v2 is 'other'
v3 = v1 + v2
# Triggers __len__: prints 10
print(len(v3))
# Accessing the attribute directly: prints 60 (10 + 50)
print(v3.x)
# Triggers __call__: prints "Called"
v1()
Decorators
A decorator is a function that modifies the behavior of another function or method without changing its code.
In other words, itβs a wrapper around a function.
Decorators are commonly used for logging, authentication, timing, caching, and more.
#Simple example
def decorator(func):
def wrapper():
print("Before function call")
func()
print("After function call")
return wrapper
@decorator
def say_hello():
print("Hello!")
say_hello()
##############################
#Another Example
def decorator(func):
def wrapper(*args, **kwargs):
print("Before function call")
result = func(*args, **kwargs) # Call the original function with arguments
print("After function call")
return result # Return the original function's result
return wrapper
@decorator
def say_hello(name, greeting="Hello"):
return f"{greeting}, {name}!"
# Usage
message = say_hello("John", greeting="Hi")
print("Returned message:", message)
Generators
def infinite_sequence():
result = 1
yield result
result *= 5
yield result
result *= 5
yield result
result *= 5
values = infinite_sequence()
print(type(values))
for x in values:
print(x)
Argument Parsing
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--name")
args = parser.parse_args()
print(args.name)
Encapsulation
Encapsulation is the mechanism of bundling data and methods together and restricting direct access to the internal state of an object.
class Person:
surname="Smith"
def __init__(self, name):
self.__name=name
def __PersonPersonal(self):
print("__PersonPersonal__")
@property
def Name(self):
return self.__name
@Name.setter
def Name(self, name):
self.__name=name
def printName(self):
print(self.__name)
print(Person.surname)
@staticmethod
def sprintName():
print(Person.surname)
@staticmethod
def setsprintName(name):
Person.surname=name
def callPrivateMethod(self):
self.__PersonPersonal()
class Employee(Person):
def callPrivateMethod(self):
self.__PersonPersonal()
p = Person("john")
print(p._Person__name)
p.Name=20
print(p.Name)
p.printName()
p.sprintName()
p.setsprintName("adam")
p.sprintName()
p.callPrivateMethod()
print("\n##############\n")
e = Employee("Jamie")
e.sprintName()
e.setsprintName("Bran")
e.sprintName()
e.callPrivateMethod();
Type Hinting
# Python Type Hinting Cheat Sheet
# -------------------------------
# Basic Variable Type Hints
# -------------------------------
age: int = 25 # integer
price: float = 10.5 # float
name: str = "John" # string
is_active: bool = True # boolean
value: None = None # None type
# -------------------------------
# Function Type Hints
# -------------------------------
def add(a: int, b: int) -> int: # function takes two ints and returns int
return a + b
def print_name(name: str) -> None: # returns nothing
print(name)
# -------------------------------
# Collection Types (Python 3.9+)
# -------------------------------
numbers: list[int] = [1, 2, 3] # list of integers
names: list[str] = ["John", "Jane"] # list of strings
user_ages: dict[str, int] = {"John": 30} # dictionary (key:str, value:int)
point: tuple[int, int] = (10, 20) # tuple of two integers
unique_items: set[str] = {"a", "b", "c"} # set of strings
# -------------------------------
# Multiple Possible Types (Union)
# -------------------------------
value: int | str = "hello" # value can be int OR string
# older syntax
from typing import Union
value2: Union[int, str] = 10
# -------------------------------
# Optional Type
# -------------------------------
name2: str | None = None # variable can be string or None
# older syntax
from typing import Optional
name3: Optional[str] = None
# -------------------------------
# Function Returning Multiple Values
# -------------------------------
def get_user() -> tuple[str, int]: # returns (name, age)
return ("John", 30)
# -------------------------------
# List of Objects
# -------------------------------
class Person:
pass
people: list[Person] = [] # list containing Person objects
# -------------------------------
# Any Type (disable type checking)
# -------------------------------
from typing import Any
data: Any = "hello" # can hold any type
data = 10
data = [1, 2, 3]
# -------------------------------
# Function as Parameter
# -------------------------------
from typing import Callable
def operate(func: Callable[[int, int], int]) -> int:
return func(2, 3)
def multiply(a: int, b: int) -> int:
return a * b
result = operate(multiply)
# -------------------------------
# Type Alias
# -------------------------------
UserId = int # creating alias for int
user_id: UserId = 10
# -------------------------------
# Generic Type
# -------------------------------
from typing import TypeVar
T = TypeVar("T") # generic type
def identity(value: T) -> T: # returns same type it receives
return value
identity(10)
identity("hello")
# -------------------------------
# Class with Type Hints
# -------------------------------
class Employee:
def __init__(self, name: str, age: int):
self.name: str = name
self.age: int = age
def greet(self) -> str:
return f"Hello {self.name}"
# -------------------------------
# Older Typing Imports (Python <3.9)
# -------------------------------
from typing import List, Dict, Tuple, Set
numbers_old: List[int] = [1,2,3]
user_old: Dict[str, int] = {"age":30}
point_old: Tuple[int, int] = (10,20)
items_old: Set[str] = {"a","b"}
# -------------------------------
# Important Notes
# -------------------------------
# 1. Type hints DO NOT enforce types at runtime
# 2. They are mainly used for:
# - IDE autocomplete
# - static type checking
# - better readability
#
# 3. Tools used for checking types:
# - mypy
# - pyright
# - pylint
#
# Example: Python will still run this without error
x: int = "hello" # incorrect type but Python will not stop execution
Functions
function definition
default parameters
keyword arguments
*args
**kwargs
lambda functions
Lambda in Python is a small anonymous one-line function used to perform simple operations without defining a full function using def.
| Example | Code | Explanation |
|---|---|---|
| Basic Lambda | square = lambda x: x * x |
Creates a small function to return the square of a number. |
| Multiple Arguments | add = lambda a, b: a + b |
Returns the sum of two numbers. |
| Immediate Execution | (lambda x: x * 2)(5) |
Defines and runs a lambda function instantly. |
With map() |
list(map(lambda x: x * 2, [1,2,3])) |
Applies a function to every element in the list. |
With filter() |
list(filter(lambda x: x % 2 == 0, [1,2,3,4])) |
Filters elements that satisfy the condition. |
With sorted() |
sorted(data, key=lambda x: x["age"]) |
Sorts items based on a specific key value. |
| Convert Data Type | list(map(lambda x: int(x), ["10","20"])) |
Converts string numbers to integers. |
| Conditional Lambda | lambda x: "Even" if x % 2 == 0 else "Odd" |
Returns a value based on a condition. |
| Max Value | lambda a, b: a if a > b else b |
Returns the larger of two numbers. |
| String Transformation | list(map(lambda x: x.upper(), names)) |
Converts each string in a list to uppercase. |
| Extract Field | list(map(lambda x: x["name"], users)) |
Extracts a specific field from dictionaries. |
| Check Condition | list(filter(lambda x: x > 10, numbers)) |
Filters numbers greater than 10. |
Quick Syntax
lambda arguments: expression
recursion vs iteration
Comprehensions
Comprehensions in Python provide a concise way to create lists, sets, or dictionaries dynamically using logic instead of manually hard-coding the values.
### List comprehension
squares = [x*x for x in range(10)]
### Dict comprehension
d = {x: x*x for x in range(5)}
### Set comprehension
unique = {x for x in numbers}
### Generator comprehension
gen = (x*x for x in range(10))
Context Managers (with)
Context managers (with statement) are used to set up a resource, use it, and then clean it up reliably, even if an exception occurs
Context managers are not limited to files. They are broadly used for:
-
Resource management β files, sockets, databases.
import sqlite3 with sqlite3.connect('example.db') as conn: cursor = conn.cursor() cursor.execute("SELECT * FROM table") # connection is automatically closed ########################### import socket with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: s.connect(('example.com', 80)) s.sendall(b'GET / HTTP/1.1\r\nHost: example.com\r\n\r\n') # socket automatically closed -
Thread/multiprocessing locks β safe concurrency.
-
Temporary state changes β directories, I/O redirection.
-
Transactions β commit/rollback logic.
-
Custom reusable setups and teardowns β any code needing guaranteed cleanup.
Iterators & Iterable Protocol
You show generators, but not the iterator protocol.
#Example:
class Counter:
def __iter__(self):
return self
def __next__(self):
...
"""Explain:
iterable
iterator
__iter__
__next__
StopIteration"""
collections Module
from collections import Counter from collections import defaultdict from collections import deque from collections import namedtuple
dataclasses
enum
functools
Useful decorators and utilities.
Examples:
from functools import lru_cache
Memoization:
@lru_cache def fib(n): ...
Other utilities:
partial
reduce
wraps
itertools
Important for iteration patterns.
Example:
import itertools
for x in itertools.permutations([1,2,3]): print(x)
Common ones:
count
cycle
repeat
permutations
combinations
chain
Async Programming
Example:
import asyncio
async def main(): print("Hello") await asyncio.sleep(1) print("World")
asyncio.run(main())
Explain:
async
await
event loop
coroutine
Pattern Matching
F-Strings
name = "John" print(f"Hello {name}")
Also:
f"{value:.2f}"
Standard Library Utilities
JSON processing
The json module is part of Pythonβs Standard Library, so it is built-in and does not need installation.
JSON is a serialized representation of a Python dictionary
Conversion:
- json.loads() β JSON to dict
- json.dumps() β dict to JSON
pathlib (modern file paths)
OOP Topics Missing
Inheritance
class Animal: pass
class Dog(Animal): pass
Polymorphism
class Cat: def speak(self): print("meow")
class Dog: def speak(self): print("bark")
Abstract Classes
from abc import ABC, abstractmethod
Python Internals (Advanced but Good)
Optional but useful.
Memory Management
reference counting
garbage collector
GIL (Global Interpreter Lock)
Important since you included threads.
Explain:
why Python threads don't run CPU tasks in parallel
multiprocessing alternative
Multiprocessing (Missing but Important)
Example:
from multiprocessing import Process
Testing (Very Useful)
Example:
import unittest
or
pytest
Closure
A closure is a function that remembers the variables from its outer (enclosing) scope even after the outer function has finished executing.
Closures are useful when you want to preserve some state without using a class.
def outer_function(msg):
def inner_function():
print(msg) # inner function remembers 'msg'
return inner_function
# Create closure
closure_func = outer_function("Hello Closure!")
# Call the returned function
closure_func()
#examples
Closures vs OOP
Closures can mimic some Object-Oriented Programming concepts by capturing variables from an enclosing scope.
Closures support: - Encapsulation - Data hiding - Stateful behavior - Function-based methods
Closures do not support: - Inheritance - Polymorphism - Class hierarchies
Closures are useful when lightweight stateful behavior is required without creating full classes.