Data Structures
Lists
List Creation
# Empty list
empty_list = []
empty_list = list()
# With initial values
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True]
nested = [[1, 2], [3, 4], [5, 6]]
# List constructor
from_string = list("hello") # ['h', 'e', 'l', 'l', 'o']
from_range = list(range(5)) # [0, 1, 2, 3, 4]
from_tuple = list((1, 2, 3)) # [1, 2, 3]
List Methods
fruits = ["apple", "banana", "orange"]
# Adding elements
fruits.append("grape") # Add to end
fruits.insert(1, "mango") # Insert at index 1
fruits.extend(["kiwi", "pear"]) # Add multiple items
# Removing elements
fruits.remove("banana") # Remove first occurrence
popped = fruits.pop() # Remove and return last item
popped = fruits.pop(0) # Remove and return at index 0
fruits.clear() # Remove all elements
# Searching and counting
index = fruits.index("apple") # Find index (raises ValueError if not found)
count = fruits.count("apple") # Count occurrences
# Sorting and reversing
fruits.sort() # Sort in place (ascending)
fruits.sort(reverse=True) # Sort in place (descending)
fruits.sort(key=len) # Sort by length
fruits.reverse() # Reverse in place
# Copying
shallow_copy = fruits.copy()
shallow_copy = fruits[:]
deep_copy = copy.deepcopy(fruits) # For nested structures
List Slicing
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
# Basic slicing
numbers[2:5] # [2, 3, 4] (start:stop)
numbers[:5] # [0, 1, 2, 3, 4] (from beginning)
numbers[5:] # [5, 6, 7, 8, 9] (to end)
numbers[:] # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] (full copy)
# Step slicing
numbers[::2] # [0, 2, 4, 6, 8] (every 2nd element)
numbers[1::2] # [1, 3, 5, 7, 9] (every 2nd, starting at 1)
numbers[::-1] # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (reverse)
# Negative indexing
numbers[-1] # 9 (last element)
numbers[-3:] # [7, 8, 9] (last 3 elements)
numbers[:-3] # [0, 1, 2, 3, 4, 5, 6] (all except last 3)
List Operations
# Concatenation
list1 = [1, 2, 3]
list2 = [4, 5, 6]
combined = list1 + list2 # [1, 2, 3, 4, 5, 6]
# Repetition
repeated = [0] * 5 # [0, 0, 0, 0, 0]
pattern = [1, 2] * 3 # [1, 2, 1, 2, 1, 2]
# Membership testing
2 in [1, 2, 3] # True
4 not in [1, 2, 3] # True
# Length and min/max
len([1, 2, 3]) # 3
min([1, 2, 3]) # 1
max([1, 2, 3]) # 3
sum([1, 2, 3]) # 6
Tuples
Tuple Creation
# Empty tuple
empty_tuple = ()
empty_tuple = tuple()
# With initial values
point = (3, 4)
person = ("Alice", 30, "Engineer")
single = (42,) # Single element tuple (note comma)
without_parentheses = 1, 2, 3 # Tuple packing
# Tuple constructor
from_list = tuple([1, 2, 3]) # (1, 2, 3)
from_string = tuple("hello") # ('h', 'e', 'l', 'l', 'o')
from_range = tuple(range(5)) # (0, 1, 2, 3, 4)
Tuple Operations
coordinates = (10, 20, 30)
# Indexing and slicing (same as lists)
coordinates[0] # 10
coordinates[-1] # 30
coordinates[1:] # (20, 30)
# Tuple methods
coordinates.count(10) # 1
coordinates.index(20) # 1
# Concatenation and repetition
(1, 2) + (3, 4) # (1, 2, 3, 4)
(1, 2) * 3 # (1, 2, 1, 2, 1, 2)
# Membership testing
20 in coordinates # True
Tuple Unpacking
# Basic unpacking
point = (3, 4)
x, y = point # x=3, y=4
# Multiple values
person = ("Alice", 30, "Engineer")
name, age, job = person
# Swapping variables
a, b = 1, 2
a, b = b, a # Swap: a=2, b=1
# Using underscore for ignored values
data = (1, 2, 3, 4, 5)
first, _, third, *rest = data # first=1, third=3, rest=[4, 5]
# Extended unpacking
first, *middle, last = data # first=1, middle=[2, 3, 4], last=5
Named Tuples
from collections import namedtuple
# Define named tuple
Point = namedtuple('Point', ['x', 'y'])
Person = namedtuple('Person', 'name age job') # Space-separated
# Create instances
p = Point(3, 4)
person = Person("Alice", 30, "Engineer")
# Access fields
print(p.x, p.y) # 3 4
print(person.name) # Alice
# Convert to dictionary
person_dict = person._asdict() # {'name': 'Alice', 'age': 30, 'job': 'Engineer'}
# Replace fields (returns new instance)
older_person = person._replace(age=31)
# Get field names
Person._fields # ('name', 'age', 'job')
Dictionaries
Dictionary Creation
# Empty dictionary
empty_dict = {}
empty_dict = dict()
# With initial values
person = {"name": "Alice", "age": 30, "city": "New York"}
person = dict(name="Alice", age=30, city="New York")
# From sequences
keys = ["name", "age", "city"]
values = ["Alice", 30, "New York"]
person = dict(zip(keys, values))
# Dictionary comprehension
squares = {x: x**2 for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# From list of tuples
person = dict([("name", "Alice"), ("age", 30)])
Dictionary Methods
person = {"name": "Alice", "age": 30, "city": "New York"}
# Accessing values
person["name"] # "Alice"
person.get("age") # 30
person.get("country", "Unknown") # "Unknown" (default value)
# Adding/updating
person["job"] = "Engineer" # Add new key
person["age"] = 31 # Update existing key
person.update({"salary": 50000, "department": "IT"})
person.update(zip(["bonus", "level"], [5000, "Senior"]))
# Removing
del person["city"] # Remove key (raises KeyError if not found)
age = person.pop("age") # Remove and return value
job = person.pop("job", "Unknown") # Remove with default
person.clear() # Remove all items
# Dictionary views
person.keys() # dict_keys(['name', 'age', 'city'])
person.values() # dict_values(['Alice', 30, 'New York'])
person.items() # dict_items([('name', 'Alice'), ...])
Dictionary Operations
# Membership testing
"name" in person # True (checks keys)
"Alice" in person.values() # True (checks values)
# Length
len(person) # Number of key-value pairs
# Copying
shallow_copy = person.copy()
shallow_copy = dict(person)
# Merging dictionaries (Python 3.9+)
dict1 = {"a": 1, "b": 2}
dict2 = {"c": 3, "d": 4}
merged = dict1 | dict2 # {"a": 1, "b": 2, "c": 3, "d": 4}
# Merging with update
dict1.update(dict2) # Modifies dict1
# Dictionary unpacking
merged = {**dict1, **dict2} # Creates new dictionary
Nested Dictionaries
# Creating nested dictionaries
company = {
"name": "TechCorp",
"employees": {
"engineering": {
"alice": {"role": "Senior Dev", "salary": 80000},
"bob": {"role": "Junior Dev", "salary": 60000}
},
"sales": {
"charlie": {"role": "Sales Rep", "salary": 70000}
}
}
}
# Accessing nested values
alice_salary = company["employees"]["engineering"]["alice"]["salary"]
# Safe access with get()
alice_role = company.get("employees", {}).get("engineering", {}).get("alice", {}).get("role")
# Using defaultdict for nested structures
from collections import defaultdict
nested_dict = defaultdict(lambda: defaultdict(dict))
nested_dict["level1"]["level2"]["level3"] = "value"
Dictionary Iteration
person = {"name": "Alice", "age": 30, "city": "New York"}
# Iterate over keys
for key in person:
print(key)
for key in person.keys():
print(key)
# Iterate over values
for value in person.values():
print(value)
# Iterate over key-value pairs
for key, value in person.items():
print(f"{key}: {value}")
# Dictionary comprehension with conditions
adults = {name: age for name, age in people.items() if age >= 18}
Sets
Set Creation
# Empty set
empty_set = set() # Note: {} creates empty dict, not set
# With initial values
numbers = {1, 2, 3, 4, 5}
mixed = {1, "hello", 3.14}
# Set constructor
from_list = set([1, 2, 3, 2, 1]) # {1, 2, 3} (duplicates removed)
from_string = set("hello") # {'h', 'e', 'l', 'o'} (duplicates removed)
from_tuple = set((1, 2, 3)) # {1, 2, 3}
# Set comprehension
squares = {x**2 for x in range(5)} # {0, 1, 4, 9, 16}
evens = {x for x in range(10) if x % 2 == 0} # {0, 2, 4, 6, 8}
Set Methods
fruits = {"apple", "banana", "orange"}
# Adding elements
fruits.add("grape") # Add single element
fruits.update(["kiwi", "pear"]) # Add multiple elements
fruits.update("mango") # Add characters: 'm', 'a', 'n', 'g', 'o'
# Removing elements
fruits.remove("banana") # Remove (raises KeyError if not found)
fruits.discard("banana") # Remove (no error if not found)
popped = fruits.pop() # Remove and return arbitrary element
fruits.clear() # Remove all elements
# Copying
fruits_copy = fruits.copy()
fruits_copy = set(fruits)
Set Operations
set1 = {1, 2, 3, 4}
set2 = {3, 4, 5, 6}
set3 = {4, 5, 6, 7}
# Union (all elements)
union = set1 | set2 # {1, 2, 3, 4, 5, 6}
union = set1.union(set2)
union = set1.union(set2, set3) # Multiple sets
# Intersection (common elements)
intersection = set1 & set2 # {3, 4}
intersection = set1.intersection(set2)
# Difference (elements in first but not second)
difference = set1 - set2 # {1, 2}
difference = set1.difference(set2)
# Symmetric difference (elements in either but not both)
sym_diff = set1 ^ set2 # {1, 2, 5, 6}
sym_diff = set1.symmetric_difference(set2)
# Update operations (modify original set)
set1.update(set2) # set1 = set1 | set2
set1.intersection_update(set2) # set1 = set1 & set2
set1.difference_update(set2) # set1 = set1 - set2
set1.symmetric_difference_update(set2) # set1 = set1 ^ set2
Set Comparisons
set1 = {1, 2, 3}
set2 = {1, 2, 3, 4}
set3 = {1, 2, 3}
# Equality
set1 == set3 # True
set1 != set2 # True
# Subset and superset
set1.issubset(set2) # True (set1 <= set2)
set1 < set2 # True (proper subset)
set2.issuperset(set1) # True (set2 >= set1)
set2 > set1 # True (proper superset)
# Disjoint (no common elements)
set1.isdisjoint({4, 5, 6}) # True
Frozen Sets
# Immutable set
frozen = frozenset([1, 2, 3, 4])
frozen_from_set = frozenset({1, 2, 3})
# Can be used as dictionary keys
cache = {frozenset([1, 2, 3]): "result"}
# All set operations work, but no modification methods
union = frozen | {5, 6} # Returns new frozenset
# frozen.add(5) # AttributeError: no add method
List Comprehensions
Basic List Comprehensions
# Basic syntax: [expression for item in iterable]
squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
names = [name.upper() for name in ["alice", "bob", "charlie"]]
# With conditions
evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]
positive = [x for x in [-2, -1, 0, 1, 2] if x > 0] # [1, 2]
# String operations
words = ["hello", "world", "python"]
lengths = [len(word) for word in words] # [5, 5, 6]
capitalized = [word.capitalize() for word in words]
# Working with functions
def square(x):
return x ** 2
squared = [square(x) for x in range(5)]
Advanced List Comprehensions
# Nested comprehensions
matrix = [[i + j for j in range(3)] for i in range(3)]
# [[0, 1, 2], [1, 2, 3], [2, 3, 4]]
# Flattening nested lists
nested = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [item for sublist in nested for item in sublist]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Multiple conditions
filtered = [x for x in range(20) if x % 2 == 0 if x % 3 == 0]
# [0, 6, 12, 18]
# Conditional expressions
result = [x if x > 0 else 0 for x in [-2, -1, 0, 1, 2]]
# [0, 0, 0, 1, 2]
# Working with dictionaries
person = {"name": "Alice", "age": 30, "city": "New York"}
uppercase_values = [str(v).upper() for v in person.values()]
key_lengths = [len(k) for k in person.keys()]
Dictionary and Set Comprehensions
# Dictionary comprehensions
squares_dict = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Filter dictionary
person = {"name": "Alice", "age": 30, "city": "New York", "job": "Engineer"}
strings_only = {k: v for k, v in person.items() if isinstance(v, str)}
# Transform keys/values
upper_keys = {k.upper(): v for k, v in person.items()}
str_values = {k: str(v) for k, v in person.items()}
# Set comprehensions
unique_lengths = {len(word) for word in ["hello", "world", "hi", "python"]}
# {2, 5, 6}
# Conditional set comprehension
vowels = {char for char in "hello world" if char in "aeiou"}
# {'e', 'o'}
Generator Expressions
Basic Generator Expressions
# Generator expression syntax: (expression for item in iterable)
squares_gen = (x**2 for x in range(5)) # Generator object
print(next(squares_gen)) # 0
print(next(squares_gen)) # 1
# Memory efficient for large datasets
large_gen = (x for x in range(1000000)) # No memory allocation yet
# Convert to list when needed
squares_list = list(squares_gen)
# Use with sum, max, min, etc.
sum_of_squares = sum(x**2 for x in range(10))
max_length = max(len(word) for word in ["hello", "world", "python"])
Generator Functions
# Generator function with yield
def fibonacci(n):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
# Using generator
fib_gen = fibonacci(10)
for num in fib_gen:
print(num)
# Infinite generator
def infinite_counter(start=0):
while True:
yield start
start += 1
# Use with next() or in loop with break
counter = infinite_counter(10)
print(next(counter)) # 10
print(next(counter)) # 11
Generator Advantages
# Memory efficient - generates values on demand
def read_large_file(filename):
with open(filename, 'r') as f:
for line in f:
yield line.strip()
# Pipeline processing
def process_data(data):
for item in data:
if item.startswith('ERROR'):
yield item.upper()
# Chaining generators
log_lines = read_large_file('log.txt')
error_lines = process_data(log_lines)
# Lazy evaluation
filtered = (x for x in range(1000000) if x % 2 == 0)
first_ten = list(itertools.islice(filtered, 10))
Collections Module
defaultdict
from collections import defaultdict
# Auto-create missing keys with default values
dd = defaultdict(list)
dd['fruits'].append('apple')
dd['fruits'].append('banana')
# {'fruits': ['apple', 'banana']}
# Different default types
dd_int = defaultdict(int) # Default value: 0
dd_list = defaultdict(list) # Default value: []
dd_set = defaultdict(set) # Default value: set()
# Custom default factory
def default_value():
return "N/A"
dd_custom = defaultdict(default_value)
print(dd_custom['missing']) # "N/A"
# Grouping items
from collections import defaultdict
words = ['apple', 'banana', 'apricot', 'blueberry', 'cherry']
grouped = defaultdict(list)
for word in words:
grouped[word[0]].append(word)
# {'a': ['apple', 'apricot'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
Counter
from collections import Counter
# Count occurrences
text = "hello world"
char_count = Counter(text)
# Counter({'l': 3, 'o': 2, 'h': 1, 'e': 1, ' ': 1, 'w': 1, 'r': 1, 'd': 1})
# Count from list
fruits = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
fruit_count = Counter(fruits)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# Most common items
fruit_count.most_common(2) # [('apple', 3), ('banana', 2)]
fruit_count.most_common() # All items, most common first
# Counter operations
counter1 = Counter('abc')
counter2 = Counter('bcd')
counter1 + counter2 # Counter({'b': 2, 'c': 2, 'a': 1, 'd': 1})
counter1 - counter2 # Counter({'a': 1})
counter1 & counter2 # Counter({'b': 1, 'c': 1}) (intersection)
counter1 | counter2 # Counter({'a': 1, 'b': 1, 'c': 1, 'd': 1}) (union)
# Update counter
fruit_count.update(['apple', 'date'])
fruit_count.subtract(['apple', 'banana'])
deque (Double-ended queue)
from collections import deque
# Create deque
dq = deque([1, 2, 3])
dq = deque(maxlen=5) # Fixed-size deque
# Add elements
dq.append(4) # Add to right
dq.appendleft(0) # Add to left
dq.extend([5, 6]) # Add multiple to right
dq.extendleft([−1, −2]) # Add multiple to left
# Remove elements
right_item = dq.pop() # Remove from right
left_item = dq.popleft() # Remove from left
# Rotation
dq.rotate(1) # Rotate right
dq.rotate(-1) # Rotate left
# Other operations
dq.reverse() # Reverse deque
dq.clear() # Remove all elements
dq.count(1) # Count occurrences
dq.remove(2) # Remove first occurrence
# Use cases
# 1. Sliding window
def sliding_window(data, window_size):
window = deque(maxlen=window_size)
for item in data:
window.append(item)
if len(window) == window_size:
yield list(window)
# 2. Undo functionality
class UndoStack:
def __init__(self, maxlen=100):
self.history = deque(maxlen=maxlen)
def execute(self, action):
self.history.append(action)
def undo(self):
if self.history:
return self.history.pop()
OrderedDict
from collections import OrderedDict
# Maintains insertion order (Python 3.7+ dict does this too)
od = OrderedDict()
od['first'] = 1
od['second'] = 2
od['third'] = 3
# Move to end
od.move_to_end('first') # Move to end
od.move_to_end('second', last=False) # Move to beginning
# Pop items
last_item = od.popitem() # Remove and return last item
first_item = od.popitem(last=False) # Remove and return first item
# Reorder
od.move_to_end('key') # Move key to end
ChainMap
from collections import ChainMap
# Combine multiple mappings
defaults = {'color': 'red', 'user': 'guest'}
user_config = {'user': 'admin', 'theme': 'dark'}
cmd_args = {'color': 'blue'}
# Create chain (first mapping has priority)
config = ChainMap(cmd_args, user_config, defaults)
print(config['color']) # 'blue' (from cmd_args)
print(config['user']) # 'admin' (from user_config)
print(config['theme']) # 'dark' (from user_config)
# Add new mapping
config = config.new_child({'debug': True})
# Get underlying mappings
config.maps # List of all mappings
Advanced Operations
Memory-Efficient Techniques
# Use generators for large datasets
def process_large_file(filename):
with open(filename, 'r') as f:
for line in f:
yield line.strip().upper()
# Use itertools for efficient iteration
import itertools
# Chain multiple iterables
combined = itertools.chain([1, 2, 3], [4, 5, 6], [7, 8, 9])
# Infinite iterators
count = itertools.count(10, 2) # 10, 12, 14, 16, ...
cycle = itertools.cycle([1, 2, 3]) # 1, 2, 3, 1, 2, 3, ...
# Grouping
data = [1, 1, 2, 2, 2, 3, 3]
grouped = itertools.groupby(data)
for key, group in grouped:
print(key, list(group))
# Combinations and permutations
items = ['A', 'B', 'C']
perms = list(itertools.permutations(items, 2)) # [('A', 'B'), ('A', 'C'), ...]
combs = list(itertools.combinations(items, 2)) # [('A', 'B'), ('A', 'C'), ('B', 'C')]
Data Structure Conversion
# Convert between data structures
my_list = [1, 2, 3, 4, 5]
my_tuple = tuple(my_list) # (1, 2, 3, 4, 5)
my_set = set(my_list) # {1, 2, 3, 4, 5}
# Dictionary to list of tuples
my_dict = {'a': 1, 'b': 2, 'c': 3}
items_list = list(my_dict.items()) # [('a', 1), ('b', 2), ('c', 3)]
keys_list = list(my_dict.keys()) # ['a', 'b', 'c']
values_list = list(my_dict.values()) # [1, 2, 3]
# List of tuples to dictionary
pairs = [('a', 1), ('b', 2), ('c', 3)]
dict_from_pairs = dict(pairs) # {'a': 1, 'b': 2, 'c': 3}
# Zip lists to create dictionary
keys = ['name', 'age', 'city']
values = ['Alice', 30, 'New York']
person = dict(zip(keys, values))
Sorting and Filtering
# Sort with custom key
students = [('Alice', 85), ('Bob', 90), ('Charlie', 78)]
by_grade = sorted(students, key=lambda x: x[1]) # Sort by grade
by_name = sorted(students, key=lambda x: x[0]) # Sort by name
# Sort dictionary by value
scores = {'Alice': 85, 'Bob': 90, 'Charlie': 78}
sorted_scores = sorted(scores.items(), key=lambda x: x[1], reverse=True)
# Filter with custom conditions
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
# Multiple criteria sorting
data = [('Alice', 'A', 85), ('Bob', 'B', 90), ('Charlie', 'A', 78)]
sorted_data = sorted(data, key=lambda x: (x[1], -x[2])) # Sort by grade, then score desc
Performance Considerations
Time Complexity
# List operations
# Access: O(1)
# Append: O(1) amortized
# Insert: O(n)
# Delete: O(n)
# Search: O(n)
# Dictionary operations
# Access: O(1) average
# Insert: O(1) average
# Delete: O(1) average
# Search: O(1) average
# Set operations
# Add: O(1) average
# Remove: O(1) average
# Membership: O(1) average
# Union/Intersection: O(len(s1) + len(s2))
Memory Usage Tips
# Use generators for large datasets
large_data = (x for x in range(1000000)) # Memory efficient
# Use slots for classes with many instances
class Point:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
# Use array for homogeneous numeric data
import array
numbers = array.array('i', [1, 2, 3, 4, 5]) # More memory efficient than list
# Use sets for membership testing
large_list = list(range(1000000))
large_set = set(large_list)
# 'in' operation is O(1) for sets, O(n) for lists
Best Practices
# Use list comprehensions over loops
# Good
squares = [x**2 for x in range(10)]
# Less efficient
squares = []
for x in range(10):
squares.append(x**2)
# Use dict.get() with default values
# Good
value = my_dict.get('key', 'default')
# Less good
if 'key' in my_dict:
value = my_dict['key']
else:
value = 'default'
# Use enumerate for index and value
# Good
for i, item in enumerate(items):
print(f"{i}: {item}")
# Less good
for i in range(len(items)):
print(f"{i}: {items[i]}")
# Use zip for parallel iteration
# Good
for name, age in zip(names, ages):
print(f"{name} is {age} years old")
# Less good
for i in range(len(names)):
print(f"{names[i]} is {ages[i]} years old")
Common Patterns
Data Processing Patterns
# Group by key
from collections import defaultdict
data = [('A', 1), ('B', 2), ('A', 3), ('B', 4), ('A', 5)]
grouped = defaultdict(list)
for key, value in data:
grouped[key].append(value)
# {'A': [1, 3, 5], 'B': [2, 4]}
# Count occurrences
from collections import Counter
items = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']
counts = Counter(items)
# Counter({'apple': 3, 'banana': 2, 'cherry': 1})
# Remove duplicates while preserving order
def remove_duplicates(items):
seen = set()
result = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result
# Alternatively, use dict.fromkeys()
unique_items = list(dict.fromkeys(items))
Caching and Memoization
# Simple cache using dictionary
cache = {}
def expensive_function(n):
if n in cache:
return cache[n]
result = n ** 2 # Expensive operation
cache[n] = result
return result
# Using functools.lru_cache decorator
from functools import lru_cache
@lru_cache(maxsize=128)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)
This cheatsheet covers the essential Python data structures and their operations. Master these concepts to write efficient and readable Python code.