Your First Python Program
Python is an interpreted language — you write code and it runs immediately, no compilation needed. The built-in print() function outputs text to the console. The famous "Hello World" is traditionally the very first program any programmer writes.
>>> print("Hello, World!") Hello, World!
The >>> is the Python interactive shell prompt — it means you're typing directly into the Python interpreter. In a .py file, just write print("Hello, World!") without the prompt.
What is a Variable?
A variable is a named container that holds a value. Python is dynamically typed — you don't declare the type; Python figures it out from the value you assign. Variables are created the moment you assign a value to them with =.
age = 18 # age is of type int name = "John" # name is now of type str print(name) # prints: John
Python cannot declare a variable without assignment. Unlike C++ or Java, you can't write int age; — you must always assign a value: age = 18.
Python's Built-in Types
Python has several built-in data types organized by category. Every value in Python is an object with a type. You can check any value's type with type().
| Category | Types | Example |
|---|---|---|
| Text | str | "hello" |
| Numeric | int, float, complex | 42, 3.14, 1j |
| Sequence | list, tuple, range | [1,2,3] |
| Mapping | dict | {"key": "value"} |
| Set | set, frozenset | {"a","b","c"} |
| Boolean | bool | True, False |
| Binary | bytes, bytearray, memoryview | b"hello" |
Numeric Types
Python has three numeric types: int (whole numbers, unlimited precision), float (decimal numbers, 64-bit), and complex (numbers with a real and imaginary part: a + bj). Booleans are actually a subclass of int: True == 1 and False == 0.
x = 1 # int y = 2.8 # float z = 1j # complex >>> print(type(x)) <class 'int'> my_bool = True my_bool = False bool(0) # => False (zero is falsy) bool(1) # => True (non-zero is truthy)
Math in Python
Python supports all standard math operations. Key differences from other languages: / always returns a float (true division), while // does floor division (integer result). ** is the power/exponentiation operator. % is modulo (remainder).
result = 10 + 30 # => 40 (addition) result = 40 - 10 # => 30 (subtraction) result = 50 * 5 # => 250 (multiplication) result = 16 / 4 # => 4.0 (float division — always float!) result = 16 // 4 # => 4 (floor/integer division) result = 25 % 2 # => 1 (modulo — remainder of 25÷2) result = 5 ** 3 # => 125 (exponent — 5 to the power 3)
Python's / always produces a float: 16 / 4 = 4.0, not 4. Use // when you need an integer result. This is different from C/C++ where 16/4 = 4.
Shorthand Assignment Operators
Python supports shorthand operators that combine an operation with assignment. x += 5 is exactly equivalent to x = x + 5. Works with all arithmetic operators: +=, -=, *=, /=, //=, %=, **=. Also works on strings for concatenation.
counter = 0 counter += 10 # => 10 (same as counter = counter + 10) counter = 0 counter = counter + 10 # => 10 (verbose version — same result) message = "Part 1." message += "Part 2." # => "Part 1.Part 2." (string concat)
Converting Between Types
Type casting (or type conversion) converts a value from one type to another using built-in functions: int(), float(), str(), bool(), etc. Useful when reading user input (always a string) and you need a number.
x = int(1) # x will be 1 y = int(2.8) # y will be 2 (truncates, NOT rounds) z = int("3") # z will be 3 (string "3" → int 3)
x = float(1) # x will be 1.0 y = float(2.8) # y will be 2.8 z = float("3") # z will be 3.0 w = float("4.2") # w will be 4.2
x = str("s1") # x will be 's1' y = str(2) # y will be '2' (number → string) z = str(3.0) # z will be '3.0'
Ordered, Mutable Collections
A list is an ordered, mutable (changeable) sequence of items. Items can be of different types. Append items with .append(). Full coverage in Module 3.
mylist = [] mylist.append(1) mylist.append(2) for item in mylist: print(item) # prints 1, then 2
Conditional Branching
Python uses indentation (spaces/tabs) — NOT curly braces — to define code blocks. The if block runs when the condition is True, else when it's False. Full coverage in Module 4.
num = 200 if num > 0: print("num is greater than 0") else: print("num is not greater than 0")
Repeating Actions
for loops iterate over a sequence. range(n) generates numbers from 0 to n-1. break exits the loop early. The else block after a loop runs only if the loop completed without a break.
for item in range(6): if item == 3: break print(item) else: print("Finally finished!") # Prints: 0, 1, 2 (stops at 3 due to break, else skipped)
Reusable Code Blocks
A function is a named block of reusable code. Define with def, call by name. Full coverage in Module 6.
>>> def my_function(): ... print("Hello from a function") ... >>> my_function() Hello from a function
Creating Strings
Strings are sequences of characters. Use single ' or double " quotes — both work identically. For multi-line strings, use triple quotes """ or '''.
hello = "Hello World" # double quotes hello = 'Hello World' # single quotes — same result multi_string = """Multiline Strings Lorem ipsum dolor sit amet, consectetur adipiscing elit""" # Triple quotes preserve newlines and indentation
Characters by Position
Strings are sequences — each character has an index. Positive indices count from the start (0-based); negative indices count from the end (-1 is last). You can loop through characters with a for loop.
>>> hello = "Hello, World" >>> print(hello[1]) # => e (index 1) >>> print(hello[-1]) # => d (last character) # Loop through each character for char in "foo": print(char) # prints: f, then o, then o
Extracting Substrings
Slicing extracts a portion of a string using s[start:end] or s[start:end:step]. The start index is inclusive, end is exclusive. Omitting start/end defaults to the beginning/end of the string.
# ┌───┬───┬───┬───┬───┬───┬───┐ # | m | y | b | a | c | o | n | # └───┴───┴───┴───┴───┴───┴───┘ # 0 1 2 3 4 5 6 7 # -7 -6 -5 -4 -3 -2 -1 s = 'mybacon' s[2:5] # => 'bac' (index 2,3,4 — end is exclusive) s[0:2] # => 'my' s[:2] # => 'my' (omit start = from beginning) s[2:] # => 'bacon' (omit end = to the end) s[:] # => 'mybacon' (full copy) s[-5:-1] # => 'baco' (negative indices) # With stride (step) s = '12345' * 5 # => '1234512345...' (repeated 5 times) s[::5] # => '11111' (every 5th char) s[::-1] # => '54321...' (reversed!)
Built-in String Operations
Python strings have many built-in methods and operations. Key ones: len() for length, in/not in for membership checks, + for concatenation, * for repetition, .join() to join a list into a string, .endswith() to check endings.
# Length >>> len("Hello, World!") 13 # Repetition (multiple copies) >>> '===+' * 3 '===+===+===+' # Membership check >>> 'spam' in 'I saw spamalot!' True >>> 'spam' not in 'Holy Grail' True # Concatenation >>> 'spam' + 'egg' 'spamegg' # join — glue list items into a string >>> "#".join(["John", "Peter", "Vicky"]) 'John#Peter#Vicky' # endswith >>> "Hello, world!".endswith("!") True
Classic Formatting Methods
Before f-strings, Python used % formatting (C-style) and the .format() method. Both are still widely used in older codebases. %s = string, %d = integer, %f = float.
# % formatting (C-style) name = "John" print("Hello, %s!" % name) name, age = "John", 23 print("%s is %d years old." % (name, age)) # .format() method — 3 styles txt1 = "My name is {fname}, I'm {age}".format(fname="John", age=36) txt2 = "My name is {0}, I'm {1}".format("John", 36) # by position txt3 = "My name is {}, I'm {}".format("John", 36) # auto
Reading User Input
input() pauses the program and waits for the user to type something and press Enter. It always returns a string — use int() or float() to convert if you need a number.
>>> name = input("Enter your name: ") Enter your name: Tom >>> name 'Tom' # Convert input to a number age = int(input("Enter your age: "))
Modern String Formatting
f-strings (formatted string literals) are the modern, recommended way to format strings. Prefix the string with f and embed expressions inside {}. Any Python expression works inside the braces — variables, math, function calls.
>>> website = 'Quickref.ME' >>> f"Hello, {website}" "Hello, Quickref.ME" >>> num = 10 >>> f'{num} + 10 = {num + 10}' # expressions work! '10 + 10 = 20' >>> f"""He said {"I'm John"}""" # nested quotes "He said I'm John" >>> f'5 {"{stars}"}' # literal { } with double braces '5 {stars}'
Padding and Aligning Text
f-strings support formatting specs inside {} after a colon: {value:fill_char align width}. > = right-align, < = left-align, ^ = center. The fill character pads the remaining space.
>>> f'{"text":10}' # width 10, left-aligned (default) 'text ' >>> f'{"test":*>10}' # fill * from left (right-align) '******test' >>> f'{"test":*<10}' # fill * from right (left-align) 'test******' >>> f'{"test":*^10}' # fill * both sides (center) '***test***' >>> f'{12345:0>10}' # zero-pad a number '0000012345'
Number Base & Notation Formatting
f-strings can format numbers in different bases and notations by adding a type character after the colon. Add # to include the base prefix (0b, 0o, 0x).
>>> f'{10:b}' # binary '1010' >>> f'{10:o}' # octal '12' >>> f'{200:x}' # hexadecimal (lowercase) 'c8' >>> f'{200:X}' # hexadecimal (uppercase) 'C8' >>> f'{345600000000:e}' # scientific notation '3.456000e+11' >>> f'{65:c}' # character (ASCII 65 = 'A') 'A' >>> f'{10:#b}' # with base prefix '0b1010'
Precision, Grouping & Sign Formatting
Control decimal precision with .Nf, add thousands separator with , or _, show percentage with %, and control sign display with +.
import math f'{math.pi:.2f}' # => '3.14' (2 decimal places) f'{1000000:,.2f}' # => '1,000,000.00' (comma grouping) f'{1000000:_.2f}' # => '1_000_000.00' (underscore grouping) f'{0.25:0%}' # => '25.000000%' f'{0.25:.0%}' # => '25%' # Sign control f'{12345:+}' # => '+12345' f'{-12345:+}' # => '-12345' f'{-12345:010}' # => '-000012345' (zero-pad negative)
Creating Lists
A list is ordered, mutable, and allows duplicates. Define with square brackets [] or list(). Lists can hold mixed types.
li1 = [] # empty list li2 = [4, 5, 6] # list of ints li3 = list((1, 2, 3)) # from tuple li4 = list(range(1, 11)) # [1,2,3,4,5,6,7,8,9,10] list1 = ["apple", "banana", "cherry"] list2 = [True, False, False] # mixed types allowed
List Comprehensions & Functional Tools
List comprehensions are a concise, Pythonic way to create lists. Syntax: [expression for item in iterable if condition]. Alternatively, use filter() with a lambda function for functional-style filtering.
# filter with lambda — keep only odd numbers list(filter(lambda x : x % 2 == 1, range(1, 20))) # => [1, 3, 5, 7, 9, 11, 13, 15, 17, 19] # List comprehension — squares of odd numbers [x ** 2 for x in range(1, 11) if x % 2 == 1] # => [1, 9, 25, 49, 81] # Filter by condition — keep items > 5 [x for x in [3, 4, 5, 6, 7] if x > 5] # => [6, 7]
Adding Items
.append(item) adds a single item to the end of a list. It modifies the list in-place and returns None.
li = [] li.append(1) # li => [1] li.append(2) # li => [1, 2] li.append(4) # li => [1, 2, 4] li.append(3) # li => [1, 2, 4, 3]
Extracting Sub-lists
List slicing works exactly like string slicing: list[start:end:step]. Returns a new list — does not modify the original. Omitting indices uses start of list / end of list.
a = ['spam', 'egg', 'bacon', 'tomato', 'ham', 'lobster'] a[2:5] # => ['bacon', 'tomato', 'ham'] a[-5:-2] # => ['egg', 'bacon', 'tomato'] a[:4] # => ['spam', 'egg', 'bacon', 'tomato'] a[2:] # => ['bacon', 'tomato', 'ham', 'lobster'] a[:] # => full copy of list # With stride a[0:6:2] # => ['spam', 'bacon', 'ham'] (every 2nd) a[::-1] # => reversed list
Deleting Elements
.pop() removes and returns the last item (or item at given index). del list[index] deletes by index without returning. .remove(value) deletes the first occurrence of a value.
li = ['bread', 'butter', 'milk'] li.pop() # returns 'milk', li => ['bread', 'butter'] del li[0] # deletes 'bread', li => ['butter']
Reading Elements by Index
Access items with square bracket notation. Indices start at 0. Use negative indices to count from the end. Accessing an out-of-range index raises an IndexError.
li = ['a', 'b', 'c', 'd'] li[0] # => 'a' (first element) li[-1] # => 'd' (last element) li[4] # => IndexError: list index out of range!
Combining Lists
.extend(other) adds all items from another list to the end (modifies in-place). The + operator creates a new combined list without modifying the originals.
odd = [1, 3, 5] odd.extend([9, 11, 13]) # modifies odd in-place # odd => [1, 3, 5, 9, 11, 13] odd = [1, 3, 5] result = odd + [9, 11, 13] # creates new list # result => [1, 3, 5, 9, 11, 13], odd unchanged
Organizing List Data
.sort() sorts the list in-place (ascending by default). .reverse() reverses in-place. .count(value) counts how many times a value appears.
li = [3, 1, 3, 2, 5] li.sort() # li => [1, 2, 3, 3, 5] li.reverse() # li => [5, 3, 3, 2, 1] li.count(3) # => 2 (3 appears twice)
More Collection Types
Repeating a list with * creates a new list with repeated elements. Tuples are immutable lists. Sets store unique items only. Dictionaries store key-value pairs.
# Repeat li = ["re"] * 3 # => ['re', 're', 're'] # Tuple — immutable (can't change after creation) my_tuple = (1, 2, 3) # Set — unique items, unordered set1 = {"a", "b", "c"} # duplicates auto-removed # Dictionary — key: value pairs a = {"one": 1, "two": 2} a["one"] # => 1 a.keys() # => dict_keys(['one', 'two']) a.values() # => dict_values([1, 2]) a.update({"three": 3}) # add a new key-value pair
Multi-branch Conditionals
Python uses indentation to define blocks — no curly braces. elif is Python's "else if". Only the first matching branch runs. Comparison operators: ==, !=, <, >, <=, >=. Logical: and, or, not.
num = 5 if num > 10: print("num is totally bigger than 10.") elif num < 10: print("num is smaller than 10.") # ← this runs else: print("num is indeed 10.")
Inline Conditional Expression
Python's ternary expression: value_if_true if condition else value_if_false. Unlike C/C++, Python uses English words instead of ? and :.
a, b = 330, 200 r = "a" if a > b else "b" print(r) # => "a" (since 330 > 200 is True)
Advanced Condition Checks
not negates a boolean. is checks identity (same object), not equality. is None is the correct way to check for null/nothing in Python (not == None).
value = True if not value: print("Value is False") elif value is None: # 'is' checks identity print("Value is None") else: print("Value is True") # ← this runs
Iterating Over Sequences
Python's for loop iterates over any iterable (list, string, range, tuple, dict, etc.). At each iteration, the loop variable is assigned the current item.
primes = [2, 3, 5, 7] for prime in primes: print(prime) # Prints: 2 3 5 7
Getting Index and Value Together
enumerate() wraps an iterable and returns (index, value) pairs. Perfect when you need both the position and the item. Use tuple unpacking: for i, value in enumerate(...).
animals = ["dog", "cat", "mouse"] for i, value in enumerate(animals): print(i, value) # Prints: # 0 dog # 1 cat # 2 mouse
Condition-Controlled Loop
A while loop runs as long as its condition is True. Always ensure the condition eventually becomes False — or use break — to avoid infinite loops.
x = 0 while x < 4: print(x) x += 1 # Shorthand for x = x + 1 # Prints: 0 1 2 3
Loop Control Statements
break exits the loop immediately. continue skips the rest of the current iteration and moves to the next one.
x = 0 for index in range(10): x = index * 10 if index == 5: break # exits loop when index is 5 print(x) # Prints: 0 10 20 30 40
for index in range(3, 8): x = index * 10 if index == 5: continue # skips print when index is 5 print(x) # Prints: 30 40 60 70 (50 is skipped)
Generating Number Sequences
range(stop), range(start, stop), or range(start, stop, step) generates a lazy sequence of integers. The stop value is exclusive.
for i in range(4): print(i) # Prints: 0 1 2 3 for i in range(4, 8): print(i) # Prints: 4 5 6 7 for i in range(4, 10, 2): print(i) # Prints: 4 6 8 (step of 2)
Pairing Two Sequences
zip() combines two or more iterables element-by-element, producing tuples. Stops at the shortest iterable. Great for pairing related data.
words = ['Mon', 'Tue', 'Wed'] nums = [1, 2, 3] for w, n in zip(words, nums): print('%d:%s, ' %(n, w)) # Prints: 1:Mon, 2:Tue, 3:Wed,
Loop's else Clause
Python's for (and while) loop can have an else block. It runs only if the loop finished without hitting a break. Very useful for search patterns: did we find what we were looking for?
nums = [60, 70, 30, 110, 90] for n in nums: if n > 100: print("%d is bigger than 100" %n) break # found — skip else else: print("Not found!") # only runs if no break # Prints: 110 is bigger than 100
Defining Reusable Code
Use def to define a function. The function body is indented. Call it by name with (). Functions without a return statement implicitly return None.
def hello_world(): print('Hello, World!') hello_world() # => Hello, World!
Passing Data In and Out
Functions accept parameters (inputs) and use return to send a value back. The function stops executing when it hits return.
def add(x, y): print("x is %s, y is %s" %(x, y)) return x + y add(5, 6) # => prints "x is 5, y is 6", returns 11
Accept Any Number of Arguments
*args collects all extra positional arguments into a tuple. The name args is a convention — the * is what matters. Use when the number of inputs isn't known ahead of time.
def varargs(*args): return args # args is a tuple varargs(1, 2, 3) # => (1, 2, 3)
Named Variable Arguments
**kwargs collects all extra keyword arguments into a dictionary. The ** is what matters. Use when you want to accept named parameters without defining them all upfront.
def keyword_args(**kwargs): return kwargs # kwargs is a dict keyword_args(big="foot", loch="ness") # => {"big": "foot", "loch": "ness"}
Tuple Unpacking as Return
Python functions can return multiple values by separating them with commas — Python packs them into a tuple. Use tuple unpacking to assign them to individual variables.
def swap(x, y): return y, x # returns a tuple (y, x) x = 1; y = 2 x, y = swap(x, y) # unpack tuple into x and y # x => 2, y => 1
Optional Arguments with Defaults
Parameters can have default values, making them optional. If the caller doesn't provide them, the default is used. Default parameters must come after non-default ones.
def add(x, y=10): # y defaults to 10 if not provided return x + y add(5) # => 15 (y uses default: 10) add(5, 20) # => 25 (y is 20, overrides default)
One-line Inline Functions
A lambda is an anonymous (unnamed) function defined in a single line: lambda parameters: expression. Used for short, throwaway functions — often passed to map(), filter(), or sorted().
# Is x greater than 2? (lambda x: x > 2)(3) # => True # x² + y² (lambda x, y: x ** 2 + y ** 2)(2, 1) # => 5 # Assign to a variable for reuse square = lambda x: x ** 2 square(4) # => 16
Using External Code Libraries
Python comes with a massive standard library. Use import module_name to load it, then access its functions with module.function() dot notation.
import math print(math.sqrt(16)) # => 4.0
Selective & Wildcard Imports
from module import name imports specific items directly into your namespace — no module prefix needed. import * imports everything (not recommended for large projects as it pollutes the namespace).
from math import ceil, floor print(ceil(3.7)) # => 4 (no math. prefix needed) print(floor(3.7)) # => 3 from math import * # imports everything — use cautiously
Renaming for Convenience
Use import module as alias to give a module a shorter name. Industry standard aliases: numpy as np, pandas as pd, matplotlib.pyplot as plt.
import math as m math.sqrt(16) == m.sqrt(16) # => True (both work)
Discovering What's Available
dir(module) returns a list of all attributes, functions, and constants in a module. Great for exploring unfamiliar libraries in the interactive shell.
import math dir(math) # => ['__doc__', '__name__', 'acos', 'acosh', 'asin', ...] # Shows all functions and constants available in math
Opening and Reading Files
Use with open(filename) — the with statement automatically closes the file when done, even if an error occurs. Mode 'r' = read (default). Iterate line by line with a for loop. enumerate() adds a line number.
# Simple line-by-line read with open("myfile.txt") as file: for line in file: print(line) # With line numbers file = open('myfile.txt', 'r') for i, line in enumerate(file, start=1): print("Number %s: %s" % (i, line))
Writing & Reading Text
Mode 'w+' opens for writing AND reading (creates file if it doesn't exist, overwrites if it does). file.write() writes a string. file.read() reads the entire file as one string.
# Write contents = {"aa": 12, "bb": 21} with open("myfile1.txt", "w+") as file: file.write(str(contents)) # writes dict as string # Read back with open('myfile1.txt', "r+") as file: contents = file.read() print(contents)
Serializing Python Objects to JSON
The json module converts Python dicts/lists to JSON strings (json.dumps()) and writes them to files, and reads JSON back into Python objects (json.load()). JSON is the universal data exchange format.
import json # Write object to file as JSON contents = {"aa": 12, "bb": 21} with open("myfile2.txt", "w+") as file: file.write(json.dumps(contents)) # serialize to JSON string # Read JSON back into Python object with open('myfile2.txt', "r+") as file: contents = json.load(file) # deserialize from JSON print(contents) # => {'aa': 12, 'bb': 21}
Removing Files with os
The os module provides operating system functions. os.remove() deletes a file. Always check if it exists first with os.path.exists() to avoid errors.
import os # Delete directly (raises error if file doesn't exist) os.remove("myfile.txt") # Safe deletion: check first if os.path.exists("myfile.txt"): os.remove("myfile.txt") else: print("The file does not exist")
Removing Directories
os.rmdir() removes an empty directory. For non-empty directories (with files inside), use shutil.rmtree().
import os os.rmdir("myfolder") # only works if folder is empty # For non-empty folders: import shutil shutil.rmtree("myfolder") # removes folder and all contents
Python's Advanced Collections
heapq provides a min-heap (priority queue). collections.deque is a double-ended queue with O(1) append/pop from both sides — used as both a stack (LIFO) and queue (FIFO).
import heapq myList = [9, 5, 4, 1, 3, 2] heapq.heapify(myList) # convert to min-heap in-place O(n) print(myList) # => [1, 3, 2, 5, 9, 4] print(myList[0]) # => 1 (smallest always at index 0) heapq.heappush(myList, 10) # insert 10 O(log n) x = heapq.heappop(myList) # pop smallest O(log n) print(x) # => 1 # Max-heap trick: negate all values myList = [-val for val in [9, 5, 4, 1, 3, 2]] heapq.heapify(myList) x = heapq.heappop(myList) print(-x) # => 9 (largest)
from collections import deque q = deque([1, 2, 3]) q.append(4) # append to right → deque([1,2,3,4]) q.appendleft(0) # append to left → deque([0,1,2,3,4]) x = q.pop() # remove from right → x=4 y = q.popleft() # remove from left → y=0 q.rotate(1) # rotate 1 step right → deque([3,1,2])
Classes are Blueprints
A class is a template for creating objects. Instantiation creates an actual object from that template. pass is a no-op placeholder when you want an empty class body.
class MyNewClass: pass # empty class body — valid Python my = MyNewClass() # Class Instantiation — creates an object
Initializing Objects
__init__ is Python's constructor — called automatically when an object is created. self refers to the instance being created. Use it to set instance attributes (data specific to each object).
class Animal: def __init__(self, voice): self.voice = voice # instance attribute cat = Animal('Meow') print(cat.voice) # => Meow dog = Animal('Woof') print(dog.voice) # => Woof
Functions Attached to Objects
A method is a function defined inside a class. The first parameter is always self (the instance calling the method). Call methods with dot notation: object.method().
class Dog: def bark(self): # self is required print("Ham-Ham") charlie = Dog() charlie.bark() # => "Ham-Ham"
Shared Data Across All Instances
Class variables are defined directly in the class body (not inside __init__). They are shared by all instances — accessible via the class name or any instance.
class MyClass: class_variable = "A class variable!" # shared by all print(MyClass.class_variable) # => A class variable! x = MyClass() print(x.class_variable) # => A class variable! (also works)
Calling the Parent Class
super() gives access to the parent class's methods from inside a child class. Used to extend (not replace) the parent's behavior. Essential when overriding methods.
class ParentClass: def print_test(self): print("Parent Method") class ChildClass(ParentClass): def print_test(self): print("Child Method") super().print_test() # calls parent's version child = ChildClass() child.print_test() # Prints: # Child Method # Parent Method
String Representation & Custom Errors
__repr__ defines how your object is represented as a string (when printed). Custom exceptions extend Exception — useful for domain-specific error handling in your application.
class Employee: def __init__(self, name): self.name = name def __repr__(self): return self.name # what print() shows john = Employee('John') print(john) # => John (uses __repr__) # Custom exception class CustomError(Exception): pass # raise CustomError("something went wrong")
Same Interface, Different Behavior
Polymorphism: different classes can have methods with the same name, each behaving differently. Overriding: a child class replaces a parent method with its own version. Python always uses the most specific (child) version available.
class ParentClass: def print_self(self): print('A') class ChildClass(ParentClass): def print_self(self): print('B') # overrides parent obj_A = ParentClass() obj_B = ChildClass() obj_A.print_self() # => A obj_B.print_self() # => B (child's version used)
Child Classes Extend Parents
Inheritance lets a class inherit all attributes and methods of a parent. The child can add its own methods and override inherited ones. Python uses class Child(Parent): syntax.
class Animal: def __init__(self, name, legs): self.name = name self.legs = legs class Dog(Animal): # Dog inherits from Animal def sound(self): print("Woof!") Yoki = Dog("Yoki", 4) print(Yoki.name) # => Yoki (inherited attribute) print(Yoki.legs) # => 4 (inherited attribute) Yoki.sound() # => Woof! (Dog's own method)
Documenting Your Code
Python uses # for single-line comments. Triple-quoted strings ("""...""" or '''...''') are used as multi-line comments and docstrings (documentation). Docstrings placed right after a def or class are accessible via help().
# This is a single-line comment. """ Multiline strings can be written using three "s, and are often used as documentation. """ ''' Multiline strings can also use three single quotes — same effect. '''
Lazy Evaluation — Memory Efficient
A generator is a function that uses yield instead of return. It produces values one at a time, on demand — never stores all values in memory. Perfect for large datasets or infinite sequences.
def double_numbers(iterable): for i in iterable: yield i + i # yields one value at a time (lazy) # Generator expressions (one-liner) values = (-x for x in [1, 2, 3, 4, 5]) gen_to_list = list(values) print(gen_to_list) # => [-1, -2, -3, -4, -5]
Use () for generator expressions and [] for list comprehensions. Generator expressions use zero extra memory — they produce values on demand. Convert with list() only when you need all values at once.
Graceful Error Handling
Use try/except to catch and handle errors without crashing. raise manually triggers an exception. else runs only if no exception occurred. finally always runs — perfect for cleanup (closing files, releasing resources).
try: raise IndexError("This is an index error") # manually raise except IndexError as e: pass # handle IndexError — do recovery here except (TypeError, NameError): pass # handle multiple exception types at once else: print("All good!") # only runs if try had NO exception finally: print("We can clean up resources here") # ALWAYS runs — exception or not
Best practice: catch specific exception types, not bare except:. Bare except catches everything including keyboard interrupts and system exits, making bugs very hard to find.
File Open Modes Cheatsheet
Quick reference for all file opening modes and when to use them.
| Mode | Meaning | Creates? | Overwrites? |
|---|---|---|---|
'r' | Read only | No | No |
'w' | Write only | Yes | Yes |
'a' | Append | Yes | No (adds to end) |
'r+' | Read + Write | No | No |
'w+' | Write + Read | Yes | Yes |
'b' | Binary mode suffix | — | — |
Always use the with statement when working with files — it guarantees the file is properly closed even if an exception occurs. Never rely on the garbage collector to close files.