Skip to content

Python Overview

Python is a high-level, interpreted, dynamically-typed programming language renowned for its readability and ease of use. Its extensive standard library and vibrant ecosystem make it a popular choice across various domains, including web development, data analysis, artificial intelligence, scientific computing, and automation.

Python Syntax: Indentation Matters

Unlike many languages that use curly braces {} to define code blocks, Python uses indentation (typically four spaces). Consistent indentation is crucial for defining the scope of loops, functions, classes, and conditional statements.

# Example of indentation defining scope
x = 5
if x > 0:
print("x is positive") # This line is part of the 'if' block
print("Indentation groups statements")
else:
print("x is non-positive") # This line is part of the 'else' block
print("This line executes after the if/else block")

Immutable vs Mutable Objects

Python objects can be broadly classified based on whether their state can be changed after creation. Understanding this distinction is fundamental to how Python manages data and memory.

Immutable Objects

These objects cannot be changed after they are created. Any operation that appears to modify an immutable object actually creates a new object.

  • Numbers (int, float, complex)
  • Strings (str)
  • Tuples (tuple)
  • Frozen Sets (frozenset)
# Attempting to modify an immutable string
my_string = "Hello"
try:
my_string[0] = "J" # This will raise a TypeError
except TypeError as e:
print(f"Error: {e}")
# Reassignment creates a *new* string object
my_string = "Jello"
print(my_string) # Output: Jello

Mutable Objects

These objects can be changed in place after they are created. Modifications affect the original object directly.

  • Lists (list)
  • Dictionaries (dict)
  • Sets (set)
  • Byte Arrays (bytearray)
# Modifying a mutable list
my_list = [1, 2, 3]
print(f"Original list: {my_list}, ID: {id(my_list)}")
my_list[0] = 100 # Modifies the list in place
my_list.append(4)
print(f"Modified list: {my_list}, ID: {id(my_list)}")
# Output shows the same ID, confirming in-place modification

Core Data Types / Object Types

Python offers a rich set of built-in data types, each suited for different kinds of data and operations.

Numbers

Integers (int), floating-point (float), complex (complex). Example: 123, 3.14, 2+3j

Sequences

Ordered collections.

  • Strings (str): Immutable sequences of characters. Example: "Hello"
  • Lists (list): Mutable sequences, can hold mixed types. Example: [1, "hi", 3.0]
  • Tuples (tuple): Immutable sequences. Example: (1, "hi", 3.0)

Mappings

Dictionaries (dict): Mutable collections of unordered key-value pairs (ordered since Python 3.7). Example: {'name': 'Alice', 'age': 30}

Sets

Mutable, unordered collections of unique elements.

  • Sets (set): Example: {1, 2, 3}
  • Frozen Sets (frozenset): Immutable version. Example: frozenset([1, 2, 3])

Booleans

Logical values True and False. Internally represented as 1 and 0.

NoneType

Represents the absence of a value. The single value is None.


Numbers (int, float, complex)

Python supports integers, floating-point numbers, and complex numbers with a wide range of operations.

# Number Types & Basic Arithmetic
integer = 42 # int: whole numbers
float_num = 3.14 # float: decimal numbers
complex_num = 2 + 3j # complex: real + imaginary
x, y = 10, 3
sum_val = x + y # 13
diff = x - y # 7
product = x * y # 30
quotient = x / y # 3.3333... (float division)
floor_div = x // y # 3 (integer division)
remainder = x % y # 1 (modulus)
power = x ** y # 1000 (exponentiation)
# Built-in Number Functions & Conversions
print(abs(-25)) # 25
print(pow(2, 3)) # 8 (same as 2 ** 3)
print(divmod(10, 3)) # (3, 1) (quotient and remainder)
print(bin(10)) # '0b1010' (binary)
print(oct(10)) # '0o12' (octal)
print(hex(10)) # '0xa' (hexadecimal)
print(int('1010', 2)) # 10 (binary to decimal)
# Float Methods & Precision Issues
pi = 3.14159
print(round(pi, 2)) # 3.14
print(float.is_integer(3.0)) # True
print(0.1 + 0.2) # 0.30000000000000004 (potential precision issue)
# Math Module for Advanced Operations
import math
print(math.sqrt(16)) # 4.0
print(math.ceil(3.7)) # 4
print(math.floor(3.7)) # 3
print(math.pi) # 3.141592653589793
print(math.log(100, 10)) # 2.0
# Complex Numbers
z = 3 + 4j
print(z.real) # 3.0
print(z.imag) # 4.0
print(abs(z)) # 5.0 (magnitude)
print(z.conjugate()) # (3-4j)
# Decimal Module for High Precision
from decimal import Decimal, getcontext
getcontext().prec = 30 # Set precision
d1 = Decimal('0.1')
d2 = Decimal('0.2')
print(d1 + d2) # Decimal('0.3') - Precise!
# Random Module
import random
print(random.randint(1, 10)) # Random integer
print(random.random()) # Random float [0.0, 1.0)

Strings (str)

Strings are immutable sequences of Unicode characters, essential for handling text.

# String Creation & Basic Operations
single = 'Hello'
double = "World"
multi_line = '''This is a
multi-line string.'''
raw_string = r'C:\path\to\file' # Backslashes are literal
s1 = "Hello"
s2 = " "
s3 = "World"
concatenated = s1 + s2 + s3 # "Hello World"
repeated = s1 * 3 # "HelloHelloHello"
length = len(concatenated) # 11
# Indexing and Slicing
text = "Python"
print(text[0]) # 'P' (first character)
print(text[-1]) # 'n' (last character)
print(text[1:4]) # 'yth' (slice from index 1 up to 4)
print(text[::-1]) # 'nohtyP' (reverse the string)
# Common String Methods
message = " Learn Python Programming! "
print(message.upper()) # " LEARN PYTHON PROGRAMMING! "
print(message.lower()) # " learn python programming! "
print(message.strip()) # "Learn Python Programming!" (remove leading/trailing whitespace)
print(message.replace('Python', 'Java')) # " Learn Java Programming! "
print(message.strip().split(' ')) # ['Learn', 'Python', 'Programming!'] (split into words)
# String Checks
filename = "report.txt"
print(filename.startswith('report')) # True
print(filename.endswith('.pdf')) # False
print("12345".isdigit()) # True
print("Python".isalpha()) # True
# String Formatting (f-strings are recommended)
name = "Alice"
age = 30
f_string = f"Name: {name}, Age: {age}" # Recommended: "Name: Alice, Age: 30"
format_method = "Name: {}, Age: {}".format(name, age) # "Name: Alice, Age: 30"
percent_format = "Name: %s, Age: %d" % (name, age) # Older style: "Name: Alice, Age: 30"
print(f_string)
# Joining and Splitting
words = ['Python', 'is', 'fun']
sentence = " ".join(words) # "Python is fun"
csv_data = "apple,banana,cherry"
items = csv_data.split(',') # ['apple', 'banana', 'cherry']
print(sentence)
print(items)
# Searching within Strings
text = "Python is easy, Python is powerful"
print(text.find('Python')) # 0 (index of first occurrence)
print(text.rfind('Python')) # 17 (index of last occurrence)
print(text.count('Python')) # 2
print('easy' in text) # True

Lists (list)

Lists are mutable, ordered sequences capable of holding items of different data types. They are highly versatile and commonly used.

# List Creation
empty_list = []
numbers = [1, 2, 3, 4, 5]
mixed = [1, "hello", 3.14, True, None]
nested = [[1, 2], [3, 4]]
list_from_range = list(range(5)) # [0, 1, 2, 3, 4]
# Basic Operations & Indexing/Slicing (Similar to Strings)
nums = [10, 20, 30, 40, 50]
print(len(nums)) # 5
print(nums[0]) # 10
print(nums[-1]) # 50
print(nums[1:3]) # [20, 30]
print(nums + [60, 70]) # [10, 20, 30, 40, 50, 60, 70] (Concatenation)
print(nums * 2) # [10, 20, 30, 40, 50, 10, 20, 30, 40, 50] (Repetition)
# Modifying Lists (Mutable)
nums[0] = 100 # Replace item at index 0
print(nums) # [100, 20, 30, 40, 50]
# Common List Methods
nums.append(60) # Add item to end: [100, 20, 30, 40, 50, 60]
nums.insert(1, 15) # Insert 15 at index 1: [100, 15, 20, 30, 40, 50, 60]
nums.extend([70, 80]) # Add multiple items from iterable: [... 60, 70, 80]
popped_item = nums.pop() # Remove and return last item (80)
removed_item = nums.pop(1) # Remove and return item at index 1 (15)
nums.remove(30) # Remove first occurrence of value 30
print(nums) # [100, 20, 40, 50, 60, 70]
# Sorting and Reversing
chars = ['c', 'a', 'b']
chars.sort() # Sorts in-place: ['a', 'b', 'c']
chars.reverse() # Reverses in-place: ['c', 'b', 'a']
sorted_chars = sorted(chars) # Returns a new sorted list: ['a', 'b', 'c'] (Original unchanged)
# Other Useful Methods & Operations
numbers = [1, 2, 3, 1, 2, 1]
print(numbers.count(1)) # 3 (counts occurrences of 1)
print(numbers.index(2)) # 1 (index of first occurrence of 2)
print(min(numbers)) # 1
print(max(numbers)) # 3
print(sum(numbers)) # 10
print(2 in numbers) # True (Membership check)
# List Comprehensions (Concise way to create lists)
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]
print(squares)
print(evens)
# Copying Lists
original = [1, [2, 3]]
shallow_copy = original.copy() # Creates a new list, but nested objects are shared
import copy
deep_copy = copy.deepcopy(original) # Creates a new list and copies nested objects recursively
shallow_copy[1][0] = 99
print(original) # [1, [99, 3]] (Nested list was affected!)
print(shallow_copy) # [1, [99, 3]]
# Reset original for deep copy demo
original = [1, [2, 3]]
deep_copy[1][0] = 55
print(original) # [1, [2, 3]] (Nested list NOT affected)
print(deep_copy) # [1, [55, 3]]

Tuples (tuple)

Tuples are immutable, ordered sequences. They are often used for fixed collections of items or when data integrity is important (e.g., dictionary keys, returning multiple values from functions).

# Tuple Creation
empty_tuple = ()
single_item_tuple = (1,) # Comma is necessary!
point = (10, 20) # Represents coordinates
mixed_tuple = (1, 'hello', 3.14)
# Basic Operations & Indexing/Slicing (Read-only)
print(len(point)) # 2
print(point[0]) # 10
print(point + (30, 40)) # (10, 20, 30, 40) (New tuple created)
print(point * 2) # (10, 20, 10, 20) (New tuple created)
# Immutable Nature
try:
point[0] = 15 # This will raise a TypeError
except TypeError as e:
print(f"Error: {e}")
# Tuple Methods (Limited due to immutability)
numbers = (1, 2, 2, 3, 2)
print(numbers.count(2)) # 3
print(numbers.index(3)) # 3 (index of first occurrence)
# Tuple Unpacking
x, y = point # Assign elements to variables
print(f"x={x}, y={y}") # Output: x=10, y=20
# Returning Multiple Values from Functions
def get_coordinates():
return 15, 25 # Returns a tuple (15, 25)
lat, lon = get_coordinates()
print(f"Latitude: {lat}, Longitude: {lon}")
# Use Cases
# 1. Dictionary Keys (must be immutable)
locations = {(40.71, -74.00): "New York", (34.05, -118.24): "Los Angeles"}
# 2. Fixed Data Structures
rgb_color = (255, 0, 0) # Red
# Named Tuples for Readability
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y']) # Creates a Point class
p1 = Point(11, y=22)
print(p1.x, p1.y) # Access by name: 11 22
print(p1[0]) # Access by index: 11

Dictionaries (dict)

Dictionaries are mutable collections of key-value pairs. Keys must be unique and immutable (strings, numbers, tuples are common). Dictionaries are optimized for fast lookups by key. (Note: Dictionaries are insertion-ordered since Python 3.7).

# Dictionary Creation
empty_dict = {}
student = {'name': 'Alice', 'age': 25, 'major': 'Computer Science'}
person = dict(name='Bob', age=30) # Using dict() constructor
from_pairs = dict([('city', 'Paris'), ('country', 'France')])
# Accessing Values
print(student['name']) # 'Alice' (Raises KeyError if key not found)
print(student.get('age')) # 25 (Returns None if key not found)
print(student.get('gpa', 'N/A')) # 'N/A' (Returns default value if key not found)
# Modifying Dictionaries
student['age'] = 26 # Update existing value
student['gpa'] = 3.8 # Add new key-value pair
print(student)
# Removing Items
del student['major'] # Remove by key (Raises KeyError if not found)
gpa = student.pop('gpa') # Remove by key and return value (Raises KeyError)
item = student.popitem() # Remove and return an arbitrary (last in 3.7+) item (key, value)
print(student) # {'name': 'Alice', 'age': 26}
print(f"Popped GPA: {gpa}, Popped item: {item}")
# Getting Keys, Values, and Items (Views)
keys = student.keys() # dict_keys(['name', 'age']) - Dynamic view
values = student.values() # dict_values(['Alice', 26]) - Dynamic view
items = student.items() # dict_items([('name', 'Alice'), ('age', 26)]) - Dynamic view
# Iterating over Dictionaries
print("\nIterating:")
# Iterate over keys (default)
for key in student:
print(f"Key: {key}, Value: {student[key]}")
# Iterate over key-value pairs (recommended)
for key, value in student.items():
print(f"{key} -> {value}")
# Iterate over values
for value in student.values():
print(f"Value: {value}")
# Checking Membership
print('name' in student) # True (Checks keys)
print('Alice' in student.values()) # True (Checks values)
# Merging Dictionaries
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
# Method 1: update() modifies dict1
# dict1.update(dict2) # dict1 becomes {'a': 1, 'b': 3, 'c': 4}
# Method 2: Dictionary unpacking (Python 3.5+) creates new dict
merged = {**dict1, **dict2} # {'a': 1, 'b': 3, 'c': 4} (Later key 'b' wins)
print(merged)
# Method 3: Union operator (Python 3.9+) creates new dict
merged_union = dict1 | dict2 # {'a': 1, 'b': 3, 'c': 4}
print(merged_union)
# Dictionary Comprehensions
squares = {x: x**2 for x in range(5)} # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
print(squares)
# Nested Dictionaries
users = {
'user1': {'name': 'John', 'city': 'New York'},
'user2': {'name': 'Jane', 'city': 'London'}
}
print(users['user1']['city']) # New York

File Handling

Python provides robust tools for reading from and writing to files.

# Using 'with' statement (Recommended: ensures file is closed automatically)
file_path = 'my_file.txt'
# Writing to a file (creates or overwrites)
try:
with open(file_path, 'w', encoding='utf-8') as f:
f.write("Hello from Python!\n")
f.write("This is the second line.\n")
lines_to_write = ["Third line.\n", "Fourth line.\n"]
f.writelines(lines_to_write)
print(f"Successfully wrote to {file_path}")
except IOError as e:
print(f"Error writing file: {e}")
# Reading from a file
try:
with open(file_path, 'r', encoding='utf-8') as f:
# Method 1: Read entire content
# content = f.read()
# print("Full content:\n", content)
# Method 2: Read line by line (memory efficient for large files)
print(f"\nReading {file_path} line by line:")
for line in f:
print(line.strip()) # strip() removes leading/trailing whitespace (like newline)
# Method 3: Read all lines into a list
# f.seek(0) # Go back to the beginning of the file
# all_lines = f.readlines()
# print("\nAll lines as list:", all_lines)
except FileNotFoundError:
print(f"Error: File '{file_path}' not found.")
except IOError as e:
print(f"Error reading file: {e}")
# Appending to a file
try:
with open(file_path, 'a', encoding='utf-8') as f:
f.write("This line was appended.\n")
print(f"\nSuccessfully appended to {file_path}")
except IOError as e:
print(f"Error appending to file: {e}")
# File Modes Cheatsheet:
# 'r' : Read (default)
# 'w' : Write (truncates file if exists, creates if not)
# 'a' : Append (adds to end, creates if not)
# 'x' : Exclusive creation (fails if file exists)
# 'b' : Binary mode (for non-text files like images, executables)
# 't' : Text mode (default)
# '+' : Update (allows reading and writing, e.g., 'r+', 'w+', 'a+')
# Working with Binary Files
image_path = 'logo.png' # Assume this exists
output_path = 'logo_copy.png'
try:
with open(image_path, 'rb') as infile, open(output_path, 'wb') as outfile:
chunk_size = 4096 # Read in chunks
while True:
chunk = infile.read(chunk_size)
if not chunk:
break
outfile.write(chunk)
print(f"\nCopied binary file to {output_path}")
except FileNotFoundError:
print(f"Error: Binary file '{image_path}' not found.")
except IOError as e:
print(f"Error handling binary file: {e}")
# os module for file system operations
import os
if os.path.exists(file_path):
print(f"\nFile '{file_path}' exists.")
print(f"File size: {os.path.getsize(file_path)} bytes")
# os.rename(file_path, 'new_name.txt') # Rename
# os.remove('new_name.txt') # Delete
else:
print(f"File '{file_path}' does not exist.")

Conditional Statements (if, elif, else)

Control the flow of execution based on whether conditions are true or false.

# Basic if-elif-else structure
score = 75
if score >= 90:
grade = 'A'
elif score >= 80:
grade = 'B'
elif score >= 70:
grade = 'C' # This condition is met
elif score >= 60:
grade = 'D'
else:
grade = 'F'
print(f"Score: {score}, Grade: {grade}") # Output: Score: 75, Grade: C
# Boolean Logic: and, or, not
age = 25
has_ticket = True
if age >= 18 and has_ticket:
print("Allowed entry.")
else:
print("Entry denied.")
is_weekend = False
is_holiday = True
if is_weekend or is_holiday:
print("Enjoy your day off!")
if not is_weekend:
print("It's a weekday.")
# Truth Value Testing (Falsy vs Truthy)
# Falsy values: False, None, 0, 0.0, '', [], (), {}, set()
# All other values are Truthy.
empty_list = []
if empty_list:
print("List has items.") # This won't print
else:
print("List is empty.") # This will print
name = "Alice"
if name:
print(f"Name is: {name}") # This will print
# Ternary Conditional Operator (Conditional Expression)
status = "Adult" if age >= 18 else "Minor"
print(f"Age: {age}, Status: {status}") # Output: Age: 25, Status: Adult
# Comparing Identity (is vs ==)
# '==' checks for equality of value
# 'is' checks for identity (if two variables point to the exact same object in memory)
a = [1, 2, 3]
b = [1, 2, 3]
c = a
print(a == b) # True (values are the same)
print(a is b) # False (they are different objects in memory)
print(a is c) # True (c points to the same object as a)
# Use 'is' primarily for checking against None, True, False
config = None
if config is None:
print("Configuration not set.")
# Pattern Matching (Python 3.10+)
http_status = 404
match http_status:
case 200 | 201:
print("Success")
case 404:
print("Not Found")
case 500:
print("Server Error")
case _: # Wildcard/Default case
print("Other status code")

Loops (for, while)

Repeat blocks of code multiple times. for loops iterate over sequences, while while loops continue as long as a condition is true.

# for loop: Iterating over sequences
fruits = ['apple', 'banana', 'cherry']
print("Fruits:")
for fruit in fruits:
print(f"- {fruit}")
print("\nCharacters in 'Python':")
for char in "Python":
print(char, end=' ') # Prints P y t h o n
print()
# for loop with range()
print("\nNumbers 0 to 4:")
for i in range(5): # Generates numbers from 0 up to (but not including) 5
print(i)
print("\nNumbers 2 to 8 (step 2):")
for i in range(2, 9, 2): # Start, Stop (exclusive), Step
print(i)
# while loop: Executes as long as condition is True
count = 0
print("\nWhile loop countdown:")
while count < 3:
print(f"Count is {count}")
count += 1 # Important: Update the condition variable
# Loop Control Statements: break and continue
print("\nLoop with break:")
for i in range(10):
if i == 5:
print("Breaking loop")
break # Exit the loop immediately
print(i)
print("\nLoop with continue:")
for i in range(5):
if i == 2:
print("Skipping iteration 2")
continue # Skip the rest of this iteration and go to the next
print(i)
# else block with loops
# The else block executes if the loop completes normally (i.e., not terminated by break)
print("\nLoop with else:")
for i in range(3):
print(f"Iteration {i}")
else:
print("Loop finished normally.")
num = 5
while num > 0:
print(num)
num -= 1
# if num == 2: break # If uncommented, the else block below would NOT run
else:
print("While loop finished normally.")
# Nested Loops
print("\nNested loops:")
for i in range(2): # Outer loop
for j in ['a', 'b']: # Inner loop
print(f"i={i}, j={j}")
# Iterating with enumerate (getting index and value)
print("\nEnumerate:")
for index, fruit in enumerate(fruits):
print(f"Index {index}: {fruit}")
# Iterating with zip (combining multiple iterables)
print("\nZip:")
names = ['Alice', 'Bob']
scores = [95, 88]
for name, score in zip(names, scores):
print(f"{name} scored {score}")

Function Definitions

Functions are named blocks of reusable code that perform a specific task. They improve organization, readability, and maintainability.

# Basic function definition
def greet(name):
"""This is a docstring explaining what the function does."""
message = f"Hello, {name}!"
return message
# Calling the function
greeting = greet("World")
print(greeting) # Output: Hello, World!

Docstrings

Docstrings ("""Docstring goes here""") are crucial for documentation. They are placed immediately after the def line and explain the function’s purpose, arguments, and return value.

def calculate_area(length, width):
"""Calculates the area of a rectangle.
Args:
length (float or int): The length of the rectangle.
width (float or int): The width of the rectangle.
Returns:
float or int: The calculated area (length * width).
Returns None if inputs are invalid (e.g., negative).
"""
if length < 0 or width < 0:
return None # Handle invalid input
return length * width
# Accessing the docstring
help(calculate_area)
# print(calculate_area.__doc__)

Function Parameters and Arguments

Python offers flexible ways to define and pass arguments:

  • Positional Arguments: Passed in order, matched by position.
  • Keyword Arguments: Passed using name=value, order doesn’t matter after positional args.
  • Default Argument Values: Provide a default if an argument isn’t passed.
  • Variable-Length Arguments (*args): Collects extra positional arguments into a tuple.
  • Variable-Keyword Arguments (**kwargs): Collects extra keyword arguments into a dictionary.
def process_data(required_arg, default_arg="default_value", *args, **kwargs):
"""Demonstrates different parameter types."""
print(f"Required: {required_arg}")
print(f"Default: {default_arg}")
print(f"Extra positional (*args): {args}") # Tuple
print(f"Extra keyword (**kwargs): {kwargs}") # Dictionary
# Examples of calling
process_data(100)
# Required: 100
# Default: default_value
# Extra positional (*args): ()
# Extra keyword (**kwargs): {}
process_data(200, "custom", 1, 2, 3, key1="value1", key2="value2")
# Required: 200
# Default: custom
# Extra positional (*args): (1, 2, 3)
# Extra keyword (**kwargs): {'key1': 'value1', 'key2': 'value2'}
process_data(required_arg=300, key3="value3")
# Required: 300
# Default: default_value
# Extra positional (*args): ()
# Extra keyword (**kwargs): {'key3': 'value3'}

Anonymous Functions (lambda)

Small, one-line functions can be created using lambda. Useful for short operations, often with functions like map(), filter(), sorted().

# Lambda function to add two numbers
add = lambda x, y: x + y
print(add(5, 3)) # Output: 8
# Using lambda with sorted()
points = [(1, 5), (3, 2), (2, 8)]
# Sort points based on the second element (y-coordinate)
points_sorted_by_y = sorted(points, key=lambda point: point[1])
print(points_sorted_by_y) # Output: [(3, 2), (1, 5), (2, 8)]

Object-Oriented Programming (OOP) in Python

OOP uses classes and objects to model real-world entities and relationships, organizing code around data (attributes) and behaviors (methods).

Creating Classes

Define a class using the class keyword. The __init__ method is a special initializer (like a constructor) called when an object is created. self is the conventional name for the first parameter of instance methods, representing the instance itself.

class Dog:
# Class attribute (shared by all instances)
species = "Canis familiaris"
def __init__(self, name, breed):
"""Initializer for the Dog class."""
# Instance attributes (unique to each instance)
self.name = name
self.breed = breed
self.tricks = [] # Each dog starts with an empty list of tricks
# Instance method
def bark(self):
"""Makes the dog bark."""
print(f"{self.name} says Woof!")
# Instance method
def add_trick(self, trick):
"""Adds a trick to the dog's repertoire."""
self.tricks.append(trick)
print(f"{self.name} learned {trick}!")
# Creating instances (objects) of the Dog class
dog1 = Dog("Buddy", "Golden Retriever")
dog2 = Dog("Lucy", "Poodle")
# Accessing attributes and calling methods
print(f"{dog1.name} is a {dog1.breed}.") # Buddy is a Golden Retriever.
print(f"Species: {dog1.species}") # Species: Canis familiaris (accessing class attribute)
dog1.bark() # Buddy says Woof!
dog2.bark() # Lucy says Woof!
dog1.add_trick("fetch")
dog2.add_trick("sit")
print(f"{dog1.name}'s tricks: {dog1.tricks}") # Buddy's tricks: ['fetch']

Inheritance

Allows a class (child/subclass) to inherit attributes and methods from another class (parent/superclass), promoting code reuse. Use super() to call methods from the parent class.

# Parent class
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
raise NotImplementedError("Subclass must implement abstract method")
# Child class inheriting from Animal
class Cat(Animal):
def __init__(self, name, color):
# Call the parent's __init__ method
super().__init__(name)
self.color = color
# Override the speak method
def speak(self):
return f"{self.name} says Meow!"
# Another child class
class LoudDog(Dog): # Inherits from Dog (which has Animal's 'species')
def __init__(self, name, breed, volume):
super().__init__(name, breed) # Call Dog's __init__
self.volume = volume
# Override the bark method
def bark(self):
print(f"{self.name} says WOOF! loudly (Volume: {self.volume})")
# Add a new method specific to LoudDog
def announce(self):
print(f"I am {self.name}, the {self.breed}!")
my_cat = Cat("Whiskers", "grey")
print(my_cat.speak()) # Whiskers says Meow!
loud_buddy = LoudDog("Buddy", "Golden Retriever", 11)
loud_buddy.bark() # Buddy says WOOF! loudly (Volume: 11)
loud_buddy.add_trick("roll over")
loud_buddy.announce()
print(f"{loud_buddy.name}'s tricks: {loud_buddy.tricks}")

JavaScript vs. Python: Constructors and this/self

While both languages support OOP, their syntax and conventions differ, particularly around object initialization and instance reference.

JavaScript (`constructor`, `this`)

Uses the constructor keyword for initialization. The this keyword implicitly refers to the current instance within methods.

class Car {
constructor(make, model) {
this.make = make; // 'this' refers to the new object
this.model = model;
this.speed = 0;
}
accelerate(amount) {
this.speed += amount;
console.log(`Accelerating to ${this.speed} km/h`);
}
}
const myCar = new Car("Toyota", "Camry");
myCar.accelerate(50); // Output: Accelerating to 50 km/h
``` </Card>
<Card title="Python (`__init__`, `self`)" icon="python">
Uses the `__init__` method for initialization. The `self` parameter must be explicitly included as the first argument in instance methods to refer to the instance.
```python
class Car:
def __init__(self, make, model):
self.make = make # 'self' is the explicit reference
self.model = model
self.speed = 0
def accelerate(self, amount):
self.speed += amount
print(f"Accelerating to {self.speed} km/h")
my_car = Car("Toyota", "Camry")
my_car.accelerate(50) # Output: Accelerating to 50 km/h