Skip to content

Python Quick Guide

Python is a high-level, interpreted, interactive, and object-oriented scripting language. It emphasizes code readability with its notable use of significant indentation.

  • Interpreted: Python is processed at runtime by the interpreter. You do not need to compile your program before executing it.
  • Interactive: You can interact directly with the interpreter to write and test snippets of code.
  • Object-Oriented: Python supports object-oriented programming, allowing code to be structured around objects.
  • Beginner-Friendly: Python has a simple, easy-to-learn syntax, making it an excellent language for beginners.

Python was created by Guido van Rossum and first released in 1991. Its design philosophy emphasizes code readability and a syntax that allows programmers to express concepts in fewer lines of code than might be used in languages such as C++ or Java.

Python’s key features include:

  • Easy-to-learn & Easy-to-read: Clear syntax promotes readability and reduces the cost of program maintenance.
  • Easy-to-maintain: Python’s success is partly due to its focus on readability and simplicity.
  • A broad standard library: Python’s ‘batteries included’ philosophy provides modules for many common tasks.
  • Interactive Mode: Allows for quick testing and debugging.
  • Portable: Python code runs on various platforms like Windows, macOS, Linux, and Unix without modification.
  • Extendable: You can add low-level modules to the Python interpreter written in languages like C or C++.
  • Databases: Python provides interfaces to all major commercial databases.
  • GUI Programming: Supports GUI applications that can be created and ported to many system calls, libraries, and windows systems.
  • Scalable: Python provides a better structure and support for large programs than shell scripting.
  • Strong Community & Ecosystem: A large, active community and the extensive Python Package Index (PyPI) offer countless third-party modules.

The most up-to-date source code, binaries, documentation, news, etc., are available on the official Python website:

Python Official Website: https://www.python.org/

You can download Python for your operating system and access comprehensive documentation, available in various formats.

Python Documentation Website: https://docs.python.org/3/

Modern Python development strongly relies on package management and virtual environments. pip is the standard package installer, and venv is the standard tool for creating isolated environments:

  • Installing packages: python -m pip install <package_name>
  • Creating a virtual environment: python -m venv myenv
  • Activating an environment (Linux/macOS): source myenv/bin/activate
  • Activating an environment (Windows): myenv\Scripts\activate

Invoking the interpreter without passing a script file starts an interactive session:

$ python
Python 3.10.4 (main, Mar 31 2022, 08:41:55) [GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Type the following command at the Python prompt (>>>) and press Enter:

>>> print("Hello, Python!")

This will produce the following result:

Hello, Python!

Alternatively, save your code in a file (e.g., hello.py) and run it from the terminal:

hello.py
print("Hello, Python!")
$ python hello.py

Output:

Hello, Python!

A Python identifier is a name used to identify a variable, function, class, module, or other object. An identifier starts with a letter (A-Z or a-z) or an underscore (_), followed by zero or more letters, underscores, and digits (0-9).

Python does not allow punctuation characters such as @, $, and % within identifiers. Python is case-sensitive, meaning MyVariable and myvariable are distinct identifiers.

Common identifier naming conventions (PEP 8):

  • Class names start with an uppercase letter (e.g., MyClass).
  • Function and variable names are typically lowercase with underscores separating words (snake_case, e.g., my_variable, calculate_sum).
  • Constants are usually written in all uppercase letters (e.g., MAX_CONNECTIONS).
  • Starting an identifier with a single leading underscore (e.g., _internal_value) conventionally indicates it’s intended for internal use (a weak ‘private’ indicator).
  • Starting an identifier with two leading underscores (e.g., __strong_private) triggers name mangling, making it harder to access from outside the class.
  • Identifiers starting and ending with two underscores (e.g., __init__, __str__) are special ‘dunder’ (double underscore) methods or attributes defined by the language.

The following words are reserved in Python and cannot be used as identifiers (variable names, function names, etc.).

| False | await | else | import | pass | | None | break | except | in | raise | | True | class | finally | is | return | | and | continue | for | lambda | try | | as | def | from | nonlocal | while | | assert | del | global | not | with | | async | elif | if | or | yield |

Python uses indentation to define code blocks (like loops, functions, classes, and conditional statements), unlike many other languages that use braces {}. This enforces a readable code style.

The standard convention is to use four spaces per indentation level. All statements within the same block must have the same indentation.

Correct indentation:

if True:
print("Condition is True")
# This is also part of the 'if' block
print("Still inside the block")
else:
print("Condition is False")
print("This is outside the if/else block")

Incorrect indentation (will cause an IndentationError):

if True:
print("Answer is True")
print("This line has wrong indentation") # Error!
else:
print("Answer is False") # Also potentially wrong if inconsistent

Statements in Python typically end with a newline. However, you can continue a statement onto the next line using the line continuation character (\):

total = item_one + \
item_two + \
item_three

Statements contained within [], {}, or () do not need the line continuation character:

days = ['Monday', 'Tuesday', 'Wednesday',
'Thursday', 'Friday']
my_tuple = (1, 2, 3,
4, 5)
my_dict = {'key1': 'value1',
'key2': 'value2'}

Python accepts single ('), double ("), and triple (''' or """) quotes to denote string literals. Single and double quotes work identically. The same type of quote must start and end the string.

Triple quotes allow strings to span multiple lines and include newlines literally:

word = 'word'
sentence = "This is a sentence."
paragraph = """This is a paragraph.
It is made up of multiple lines and sentences.
Newlines are preserved."""
print(word)
print(sentence)
print(paragraph)

Comments start with a hash symbol (#) and extend to the end of the physical line. The Python interpreter ignores comments. They are used to explain code.

#!/usr/bin/env python3
# This is a full-line comment
print("Hello, Python!") # This is an inline comment
# Another comment
# spanning multiple lines.

This will produce the following result:

Hello, Python!

Multi-line comments can also be approximated using triple-quoted strings, although these are technically string literals and not true comments. They are often used for docstrings.

"""This is often used as a multi-line comment
or documentation string (docstring).
It's actually a string literal, but ignored if not assigned.
"""
def my_function():
"""This is a docstring explaining the function."""
pass

Blank lines (containing only whitespace, possibly with a comment) are ignored by Python. They are used to improve code readability by separating logical blocks of code.

In the interactive interpreter, an empty physical line is sometimes needed to terminate a multiline statement (e.g., after defining a function or class).

The semicolon (;) allows multiple statements on a single line, provided neither statement starts a new code block. This is generally discouraged as it can reduce readability.

import sys; x = 'foo'; sys.stdout.write(x + '\n') # Works, but less readable

Prefer one statement per line:

import sys
x = 'foo'
sys.stdout.write(x + '\n')

A group of statements forming a single code block under a control structure (like if, while, def, class) is called a ‘suite’ in Python.

Compound statements consist of a header line ending with a colon (:) followed by an indented suite.

if expression:
# This is the suite for the 'if' block
statement1
statement2
elif expression:
# Suite for the 'elif' block
statement3
else:
# Suite for the 'else' block
statement4

Variables are names that refer to values stored in memory. When you create a variable, you associate a name with a value.

Python uses dynamic typing, meaning you don’t need to explicitly declare the type of a variable. The type is inferred from the value assigned to it. The interpreter allocates memory based on the data type.

The assignment operator (=) is used to assign values to variables. The variable name is on the left, and the value is on the right.

counter = 100 # An integer assignment
miles = 1000.0 # A floating-point assignment
name = "Alice" # A string assignment
is_active = True # A boolean assignment
print(counter)
print(miles)
print(name)
print(is_active)

Multiple assignment is also possible:

a = b = c = 1 # Assign 1 to a, b, and c
x, y, z = 1, 2, "hello" # Assign multiple values to multiple variables
print(a, b, c)
print(x, y, z)

Python has several built-in data types. The main categories are:

  • Numeric Types: int (integers), float (floating-point numbers), complex (complex numbers). Note: Python 3’s int can handle arbitrarily large integers.
  • Boolean Type: bool (True or False).
  • Sequence Types: str (strings), list (mutable sequences), tuple (immutable sequences).
  • Set Types: set (unordered collection of unique items), frozenset (immutable version of set).
  • Mapping Type: dict (dictionaries, key-value pairs).
  • None Type: NoneType (represents the absence of a value, has only one value: None).

You can check the type of a variable using the type() function:

num = 10
print(type(num)) # Output: <class 'int'>
pi = 3.14
print(type(pi)) # Output: <class 'float'>
message = "Hi"
print(type(message)) # Output: <class 'str'>

Number objects are created when you assign a numeric value.

var1 = 1
var2 = 10.5
var3 = 3 + 4j # Complex number
print(var1, type(var1))
print(var2, type(var2))
print(var3, type(var3))

Python supports standard numeric types:

  • int: Signed integers of unlimited size (e.g., 10, -786, 0, 12345678901234567890).
  • float: Floating-point real values (e.g., 15.20, -21.9, 3.14e-10).
  • complex: Complex numbers with real and imaginary parts (e.g., 3.14j, 45.j, 9.322e-36j, 3+4j).

Strings in Python 3 are sequences of Unicode characters, enclosed in single or double quotes. They are immutable, meaning their content cannot be changed after creation.

my_str = 'Hello World!'
print(my_str) # Prints complete string: Hello World!
print(my_str[0]) # Prints first character: H
print(my_str[6:11]) # Prints characters from index 6 up to (but not including) 11: World
print(my_str[6:]) # Prints string starting from index 6: World!
print(my_str * 2) # Prints string two times: Hello World!Hello World!
print(my_str + " TEST") # Prints concatenated string: Hello World! TEST
# Trying to change a character will cause an error:
# my_str[0] = 'J' # TypeError: 'str' object does not support item assignment

Modern string formatting uses f-strings (Python 3.6+):

name = "Alice"
age = 30
print(f"My name is {name} and I am {age} years old.")
# Output: My name is Alice and I am 30 years old.

Lists are versatile, mutable (changeable) sequences enclosed in square brackets []. Items are separated by commas and can be of different data types.

my_list = ['abcd', 786, 2.23, 'john', 70.2]
tiny_list = [123, 'john']
print(my_list) # Prints complete list: ['abcd', 786, 2.23, 'john', 70.2]
print(my_list[0]) # Prints first element: abcd
print(my_list[1:3]) # Prints elements from index 1 up to 3: [786, 2.23]
print(my_list[2:]) # Prints elements starting from index 2: [2.23, 'john', 70.2]
print(tiny_list * 2) # Prints list two times: [123, 'john', 123, 'john']
print(my_list + tiny_list) # Prints concatenated lists: ['abcd', 786, 2.23, 'john', 70.2, 123, 'john']
# Lists are mutable
my_list[2] = 'updated'
print(my_list) # Output: ['abcd', 786, 'updated', 'john', 70.2]

A tuple is an immutable (unchangeable) sequence enclosed in parentheses (). Items are separated by commas.

Tuples are often used for fixed collections of items and can be used as keys in dictionaries (unlike lists).

my_tuple = ('abcd', 786, 2.23, 'john', 70.2)
tiny_tuple = (123, 'john')
print(my_tuple) # Prints complete tuple: ('abcd', 786, 2.23, 'john', 70.2)
print(my_tuple[0]) # Prints first element: abcd
print(my_tuple[1:3]) # Prints elements from index 1 up to 3: (786, 2.23)
print(my_tuple[2:]) # Prints elements starting from index 2: (2.23, 'john', 70.2)
print(tiny_tuple * 2) # Prints tuple two times: (123, 'john', 123, 'john')
print(my_tuple + tiny_tuple) # Prints concatenated tuples: ('abcd', 786, 2.23, 'john', 70.2, 123, 'john')
# Trying to change an element will cause an error:
# my_tuple[0] = 'xyz' # TypeError: 'tuple' object does not support item assignment
# Single element tuple needs a comma:
single_item_tuple = (50,) # This is a tuple
not_a_tuple = (50) # This is just the integer 50 in parentheses

Dictionaries (dict) are unordered (in Python < 3.7) or ordered (Python 3.7+) collections of key-value pairs, enclosed in curly braces {}. Keys must be unique and immutable (like strings, numbers, or tuples). Values can be any data type.

my_dict = {'name': 'john', 'code': 6734, 'dept': 'sales'}
print(my_dict['name']) # Prints value for 'name' key: john
print(my_dict['code']) # Prints value for 'code' key: 6734
# print(my_dict[2]) # Error! Keys are used for access, not integer indices unless the key *is* an integer.
print(my_dict) # Prints complete dictionary: {'name': 'john', 'code': 6734, 'dept': 'sales'}
print(my_dict.keys()) # Prints a view of all keys: dict_keys(['name', 'code', 'dept'])
print(my_dict.values()) # Prints a view of all values: dict_values(['john', 6734, 'sales'])
print(my_dict.items()) # Prints a view of key-value pairs: dict_items([('name', 'john'), ('code', 6734), ('dept', 'sales')])
# Adding or updating items
my_dict['age'] = 35 # Add a new key-value pair
my_dict['dept'] = 'IT' # Update value for existing key 'dept'
print(my_dict)

Operators perform operations on variables and values. Python includes Arithmetic, Comparison, Assignment, Logical, Bitwise, Membership, and Identity operators.

OperatorNameDescriptionExample
+AdditionAdds values on either sidea + b
-SubtractionSubtracts right operand from lefta - b
*MultiplicationMultiplies valuesa * b
/True DivisionDivides left operand by right, result is always floatb / a (e.g., 10 / 4 is 2.5)
%ModulusReturns remainder of divisionb % a
**ExponentiationRaises left operand to power of righta**b
//Floor DivisionDivides and rounds down to nearest whole numberb // a (e.g., 10 // 4 is 2, -10 // 4 is -3)
==Equal toChecks if values are equala == b
!=Not equal toChecks if values are not equala != b
>Greater thanChecks if left operand is greater than righta > b
<Less thanChecks if left operand is less than righta < b
>=Greater than or equal toChecks if left operand is greater than or equal to righta >= b
<=Less than or equal toChecks if left operand is less than or equal to righta <= b
=AssignmentAssigns value from right to left operandc = a + b
+=Add ANDAdds right operand to left and assigns result to leftc += a (same as c = c + a)
-=Subtract ANDSubtracts right from left and assigns result to leftc -= a
*=Multiply ANDMultiplies left by right and assigns result to leftc *= a
/=Divide ANDDivides left by right and assigns result (float) to leftc /= a
%=Modulus ANDTakes modulus and assigns result to leftc %= a
**=Exponent ANDRaises left to power of right and assigns result to leftc **= a
//=Floor Divide ANDPerforms floor division and assigns result to leftc //= a
&Bitwise ANDPerforms bitwise ANDa & b
|Bitwise ORPerforms bitwise ORa | b
^Bitwise XORPerforms bitwise XORa ^ b
~Bitwise NOTFlips bits (unary)~a
<<Bitwise Left ShiftShifts bits lefta << 2
>>Bitwise Right ShiftShifts bits righta >> 2
andLogical ANDTrue if both operands are truea > 5 and b < 10
orLogical ORTrue if at least one operand is truea > 5 or b < 10
notLogical NOTReverses the logical statenot (a > 5)
inMembershipTrue if value is found in sequencex in my_list
not inMembershipTrue if value is not found in sequencex not in my_list
isIdentityTrue if both variables point to the same object in memoryx is y
is notIdentityTrue if variables point to different objectsx is not y

Note: The <> operator is removed in Python 3; use != instead.

Operators are evaluated in a specific order (precedence). Use parentheses () to override precedence or improve clarity.

OperatorDescription
()Parentheses (highest precedence)
f(args...), x[index], x[index:index], x.attributeFunction calls, slicing, subscripting, attribute reference
await xAwait expression
**Exponentiation (right-associative)
+x, -x, ~xPositive, negative, bitwise NOT
*, /, //, %Multiplication, division, floor division, modulus
+, -Addition and subtraction
<<, >>Bitwise shifts
&Bitwise AND
^Bitwise XOR
|Bitwise OR
in, not in, is, is not, <, <=, >, >=, !=, ==Comparisons, including membership and identity tests
not xBoolean NOT
andBoolean AND
orBoolean OR
if elseConditional expression
lambdaLambda expression
:=Assignment expression (Walrus operator, Python 3.8+)

Note: Operators in the same box evaluate left to right, except for exponentiation (**) which is right-associative.

Conditional statements allow code execution based on whether a condition is true or false.

Executes a block of code only if the condition is true.

if expression:
statement(s)

Executes one block if the condition is true, and another block if it’s false.

if expression:
statement(s)_if_true
else:
statement(s)_if_false

Checks multiple conditions in sequence. elif is short for ‘else if’.

if expression1:
statement(s)_1
elif expression2:
statement(s)_2
elif expression3:
statement(s)_3
# ... more elif blocks ...
else:
statement(s)_else

You can place if, elif, or else statements inside other conditional blocks.

if expression1:
# Outer if block
if expression2:
# Inner if block
statement(s)_A
else:
# Inner else block
statement(s)_B
else:
# Outer else block
statement(s)_C

Loops execute a block of code repeatedly.

Repeats a block as long as a condition remains true.

while expression:
statement(s)

Example:

count = 0
while count < 5:
print(f"Count is: {count}")
count += 1 # Important: update the counter to avoid infinite loop
print("Loop finished")

Be careful! If the condition in a while loop never becomes false, it will run forever (an infinite loop). Press Ctrl+C to interrupt it. Sometimes infinite loops are intentional (e.g., in servers waiting for connections), often using break to exit under specific conditions.

Iterates over the items of any sequence (like a list, tuple, string, or dictionary keys/values) or other iterable object.

for item in sequence:
statements(s)
fruits = ['banana', 'apple', 'mango']
for fruit in fruits:
print(f"Current fruit: {fruit}")
print("Goodbye!")

The range() function generates a sequence of numbers, useful for looping a specific number of times.

# range(stop) - goes from 0 up to (but not including) stop
for i in range(5):
print(i) # Output: 0, 1, 2, 3, 4
# range(start, stop) - goes from start up to stop
for i in range(2, 6):
print(i) # Output: 2, 3, 4, 5
# range(start, stop, step) - increments by step
for i in range(0, 10, 2):
print(i) # Output: 0, 2, 4, 6, 8

You can combine range() and len() to iterate using indices, although direct iteration over items is often more ‘Pythonic’.

fruits = ['banana', 'apple', 'mango']
for index in range(len(fruits)):
print(f"Index: {index}, Fruit: {fruits[index]}")
# More Pythonic way using enumerate():
for index, fruit in enumerate(fruits):
print(f"Index: {index}, Fruit: {fruit}")

Terminates the current loop prematurely and transfers control to the statement immediately following the loop.

for letter in 'Python':
if letter == 'h':
break
print(f'Current Letter: {letter}')
# Output: Current Letter: P, Current Letter: y, Current Letter: t
num = 10
while num > 0:
print(f'Current variable value: {num}')
num -= 1
if num == 5:
break # Exit loop when num becomes 5
print("Goodbye!")

Skips the rest of the code inside the current loop iteration and proceeds to the next iteration.

for letter in 'Python':
if letter == 'h':
continue # Skip 'h'
print(f'Current Letter: {letter}')
# Output: Current Letter: P, Current Letter: y, Current Letter: t, Current Letter: o, Current Letter: n
num = 10
while num > 0:
num -= 1
if num == 5:
continue # Skip printing when num is 5
print(f'Current variable value: {num}')
print("Goodbye!")

Python loops can have an optional else clause. The else block executes only if the loop completes normally (i.e., not terminated by a break statement).

for i in range(5):
print(i)
# if i == 3: break # Uncommenting this would skip the else block
else:
print("Loop finished normally")
count = 0
while count < 3:
print(count)
count += 1
# if count == 2: break # Uncommenting this would skip the else block
else:
print("While loop finished normally")

The pass statement is a null operation; nothing happens when it executes. It acts as a placeholder where syntax requires a statement, but no action is needed.

Useful for empty code blocks during development or in abstract classes.

def my_empty_function():
pass # Avoids SyntaxError for an empty function body
class MyEmptyClass:
pass
for letter in 'Python':
if letter == 'h':
pass # Placeholder, maybe add code later
print('This is pass block for h')
print(f'Current Letter: {letter}')
print("Goodbye!")

Functions are blocks of reusable code that perform a specific task. They help organize code, make it more readable, and reduce redundancy.

Rules for defining a function in Python:

  • Function blocks begin with the keyword def, followed by the function name and parentheses (). The name follows standard identifier rules.
  • Input parameters (arguments) are placed within the parentheses. Parameters are optional.
  • The function header line ends with a colon (:).
  • The code block (suite) within the function must be indented.
  • An optional documentation string (docstring), usually a triple-quoted string, can be the first statement. It explains what the function does.
  • The return statement exits the function and optionally sends a value back to the caller. If return is omitted or used without an expression, the function returns None.
def function_name(parameters):
"""Optional docstring explaining the function."""
# Function suite (indented code block)
statement1
statement2
# ...
return [expression] # Optional return value

By default, arguments are passed positionally, matching the order they are defined.

A simple function that takes a string and prints it:

def print_message(message):
"""Prints the passed string to the console."""
print(message)
# No explicit return statement means it returns None

A function that returns a value:

def add(a, b):
"""Returns the sum of two numbers."""
return a + b

Python 3 allows adding type hints (PEP 484) to function signatures for better code clarity and tooling support (like static analysis).

def greet(name: str) -> str:
"""Returns a greeting string for the given name."""
return f"Hello, {name}!"
def calculate_area(length: float, width: float) -> float:
"""Calculates the area of a rectangle."""
return length * width

Defining a function doesn’t execute it. You need to ‘call’ the function by using its name followed by parentheses (), passing any required arguments inside.

Example using the functions defined above:

# Function definition is here
def print_message(message: str):
"""Prints the passed string."""
print(message)
def add(a: int, b: int) -> int:
"""Returns the sum of two integers."""
return a + b
# Now call the functions
print_message("Calling the print_message function!")
print_message("Second call to the same function.")
sum_result = add(5, 3)
print(f"The sum is: {sum_result}")

This would produce the following result:

Calling the print_message function!
Second call to the same function.
The sum is: 8

Modules are Python files (.py) containing Python definitions and statements (functions, classes, variables). They allow you to logically organize code and reuse it across different programs.

Grouping related code into modules makes projects easier to understand, maintain, and scale.

Create a file named my_module.py with the following content:

my_module.py
def greet(name: str):
"""Prints a greeting."""
print(f"Hello, {name} from my_module!")
PI = 3.14159

To use the definitions from a module in another Python script, you use the import statement.

module_name.member
# Option 1: Import the entire module
import module_name
# Option 2: Import specific names from a module
from module_name import name1, name2
# Access members directly by name: name1
# Option 3: Import all names (generally discouraged due to namespace pollution)
# from module_name import *
# Option 4: Import with an alias
import module_name as alias
from module_name import long_name as short_name

When Python encounters an import statement, it searches for the module in a specific list of directories (sys.path), including the current script’s directory and standard library locations. The module is loaded and executed only once per interpreter session.

Create another file, e.g., main_script.py, in the same directory:

main_script.py
# Import the entire module
import my_module
# Import a specific name
from my_module import PI
# Call the function using module name
my_module.greet("Alice")
# Access the constant directly (since it was imported specifically)
print(f"The value of PI is approximately: {PI}")

Running main_script.py produces:

Hello, Alice from my_module!
The value of PI is approximately: 3.14159

Packages are a way of structuring Python’s module namespace using ‘dotted module names’. A package is simply a directory containing Python modules and a special __init__.py file (which can be empty) to mark the directory as a package.

Example structure:
my_package/
__init__.py
module1.py
module2.py
sub_package/
__init__.py
module3.py

You can import from packages like this:

import my_package.module1
from my_package.sub_package import module3
from my_package.module2 import specific_function

To read or write files, you first need to open them using the built-in open() function. The recommended way to do this is using the with statement, which automatically handles closing the file, even if errors occur.

with open(file_path, mode='r', encoding=None) as file_object:
# Perform operations on file_object (read, write, etc.)
# File is automatically closed when exiting the 'with' block

Parameters:

  • file_path: A string containing the path to the file.
  • mode: Specifies the purpose of opening the file (optional, defaults to 'r'):
  • 'r': Read (default). Error if file doesn’t exist.
  • 'w': Write. Creates file if it doesn’t exist, truncates (empties) if it does.
  • 'a': Append. Creates file if it doesn’t exist, adds data to the end.
  • 'x': Exclusive creation. Creates file, error if it already exists.
  • 'b': Binary mode (append to other modes, e.g., 'rb', 'wb'). For non-text files.
  • 't': Text mode (default, append as 'rt', 'wt').
  • '+': Update (reading and writing, e.g., 'r+', 'w+').
  • encoding: The encoding used for text files (optional but highly recommended for portability). Common values: 'utf-8', 'ascii', 'latin-1'. If omitted, the system default is used, which can lead to errors.
file_path = "my_file.txt"
# Write text to a file (overwrites existing content or creates new file)
with open(file_path, 'w', encoding='utf-8') as f:
f.write("This is the first line.\n")
f.write("This is the second line.\n")
print(f"Successfully wrote to {file_path}")
# Read the entire file content
print("\nReading entire file:")
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
print(content)
# Read file line by line
print("\nReading line by line:")
with open(file_path, 'r', encoding='utf-8') as f:
for line in f:
print(line, end='') # Use end='' to avoid double newlines

Once a file is opened, the file object has useful attributes:

AttributeDescription
file.closedReturns True if the file is closed, False otherwise.
file.modeReturns the access mode with which the file was opened.
file.nameReturns the name of the file.
  • read(size=-1): Reads size bytes/characters, or the entire file if size is negative or omitted.
  • readline(): Reads a single line, including the newline character (\n).
  • readlines(): Reads all lines into a list of strings.
  • write(string): Writes the given string to the file.
  • writelines(list_of_strings): Writes each string from the list to the file (doesn’t add newlines automatically).
  • seek(offset, whence=0): Changes the file position. whence=0 (default) for absolute position, 1 for relative to current, 2 for relative to end.
  • tell(): Returns the current file position.
  • flush(): Flushes the internal buffer to the disk.
  • close(): Closes the file (automatically handled by with statement).

The os module provides functions for interacting with the operating system, including file system operations. The newer pathlib module offers an object-oriented approach, often preferred in modern code.

import os
from pathlib import Path
old_name = "my_file.txt"
new_name = "renamed_file.txt"
dir_name = "my_new_directory"
# Rename a file
# os.rename(old_name, new_name)
# Path(old_name).rename(new_name)
# Delete a file
# os.remove(new_name) # Assuming renamed first
# Path(new_name).unlink()
# Create a directory
# os.mkdir(dir_name)
# Path(dir_name).mkdir(exist_ok=True) # exist_ok=True prevents error if dir exists
# Get current working directory
print(f"Current dir (os): {os.getcwd()}")
print(f"Current dir (pathlib): {Path.cwd()}")
# Change directory
# os.chdir(dir_name) # Change into the new directory
# print(f"Changed dir to: {os.getcwd()}")
# os.chdir("..") # Change back up
# Remove a directory (must be empty)
# os.rmdir(dir_name)
# Path(dir_name).rmdir()
# Check if path exists
print(f"Does {old_name} exist? {os.path.exists(old_name)}")
print(f"Does {old_name} exist? {Path(old_name).exists()}")
# Check if it's a file or directory
# print(f"Is {old_name} a file? {os.path.isfile(old_name)}")
# print(f"Is {old_name} a file? {Path(old_name).is_file()}")
# print(f"Is {dir_name} a dir? {os.path.isdir(dir_name)}")
# print(f"Is {dir_name} a dir? {Path(dir_name).is_dir()}")

Errors detected during execution are called exceptions. Python provides mechanisms to handle these exceptions gracefully, preventing program crashes.

Place code that might raise an exception inside the try block. If an exception occurs, the corresponding except block is executed.

try:
# Code that might raise an exception
risky_operation()
except SpecificExceptionType1:
# Handle SpecificExceptionType1
print("Caught exception type 1!")
except AnotherExceptionType as e:
# Handle AnotherExceptionType and access the exception object 'e'
print(f"Caught exception type 2: {e}")
except (ExceptionType3, ExceptionType4):
# Handle multiple exception types in one block
print("Caught exception type 3 or 4!")
except Exception as e: # Catch any other standard exception (use cautiously)
print(f"Caught some other standard exception: {e}")
else:
# Optional: Executes only if the 'try' block completes without errors
print("No exceptions were raised.")
finally:
# Optional: Always executes, whether an exception occurred or not
# Used for cleanup actions (e.g., closing resources if not using 'with')
print("This finally block always runs.")
try:
numerator = 10
denominator = int(input("Enter a denominator: "))
result = numerator / denominator
except ZeroDivisionError:
print("Error: Cannot divide by zero.")
except ValueError:
print("Error: Please enter a valid integer.")
except Exception as e:
print(f"An unexpected error occurred: {e}")
else:
print(f"The result is: {result}")
finally:
print("Calculation attempt finished.")

Common Built-in Exceptions:

  • SyntaxError
  • IndentationError
  • NameError
  • TypeError
  • ValueError
  • IndexError
  • KeyError
  • FileNotFoundError
  • ZeroDivisionError
  • AttributeError
  • ImportError

For a full list, see the Python documentation on Built-in Exceptions.

You can manually raise exceptions using the raise statement.

raise ExceptionType("Optional error message")
def check_age(age):
if age < 0:
raise ValueError("Age cannot be negative.")
elif age < 18:
print("User is underage.")
else:
print("User is an adult.")
try:
check_age(-5)
except ValueError as e:
print(f"Error checking age: {e}")

Create custom exception types by defining classes that inherit from Exception or one of its subclasses.

class CustomError(Exception):
"""A custom exception class."""
def __init__(self, message="A custom error occurred."):
self.message = message
super().__init__(self.message)
try:
# Simulate a condition causing the custom error
raise CustomError("Something specific went wrong.")
except CustomError as e:
print(f"Caught custom error: {e.message}")

Python is an object-oriented language. OOP allows structuring programs around ‘objects’, which combine data (attributes) and behavior (methods).

A class is a blueprint for creating objects. Use the class keyword.

class ClassName:
"""Optional docstring for the class."""
# Class attribute (shared by all instances)
class_variable = "value"
# Initializer method (constructor)
def __init__(self, param1, param2):
print("Initializing instance...")
# Instance attributes (unique to each instance)
self.attribute1 = param1
self.attribute2 = param2
# Instance method
def method1(self):
print(f"Method 1 called for instance with attribute1: {self.attribute1}")
# Access class variable via self or ClassName
print(f"Accessing class variable: {self.class_variable} or {ClassName.class_variable}")
# Another instance method
def method2(self, param):
print(f"Method 2 called with parameter: {param}")
# Special method for string representation
def __str__(self):
return f"Instance of ClassName(attribute1={self.attribute1})"

Key Concepts:

  • class: Keyword to define a class.
  • ClassName: Follows identifier rules, typically CapWords (CamelCase).
  • __init__(self, ...): Special initializer method called when an object is created. self refers to the instance being created.
  • self: The first parameter in instance methods, representing the instance itself.
  • Attributes: Variables associated with a class (class attributes) or an instance (instance attributes).
  • Methods: Functions defined inside a class that operate on instances.

To create an object (instance) of a class, call the class name as if it were a function, passing arguments required by __init__ (excluding self).

# Create instances of ClassName
instance1 = ClassName("value A", 100)
instance2 = ClassName("value B", 200)

Use the dot operator (.) to access an instance’s attributes and methods.

# Access instance attributes
print(instance1.attribute1) # Output: value A
print(instance2.attribute2) # Output: 200
# Call instance methods
instance1.method1()
instance2.method2("Test parameter")
# Access class attribute (can be accessed via instance or class)
print(instance1.class_variable) # Output: value
print(ClassName.class_variable) # Output: value
# Print the instance (uses __str__ method)
print(instance1) # Output: Instance of ClassName(attribute1=value A)

Python classes have several built-in attributes:

  • __dict__: Dictionary containing the instance’s (or class’s) namespace.
  • __doc__: Class documentation string or None.
  • __name__: Class name (string).
  • __module__: Module name where the class is defined.
  • __bases__: Tuple containing the base classes.
print(f"Docstring: {ClassName.__doc__}")
print(f"Class name: {ClassName.__name__}")
print(f"Instance 1 dict: {instance1.__dict__}")

Python automatically manages memory using garbage collection. When an object is no longer referenced (its reference count drops to zero), Python reclaims its memory.

You don’t usually need to manually delete objects. The del statement removes a reference, which might trigger garbage collection if it was the last reference.

a = [1, 2, 3]
b = a # Both 'a' and 'b' refer to the same list
print(f"Object ID for a: {id(a)}")
print(f"Object ID for b: {id(b)}")
del a # Remove reference 'a'
print("Reference 'a' deleted.")
# The list object still exists because 'b' references it
print(b)
del b # Remove reference 'b'. Now the list object has no references and can be garbage collected.
print("Reference 'b' deleted.")
# print(b) # NameError: name 'b' is not defined

The __del__ method can define cleanup actions when an object is about to be destroyed, but its exact timing is not guaranteed, making it less reliable than with statements or try...finally for critical cleanup.

Inheritance allows a new class (child or subclass) to inherit attributes and methods from an existing class (parent or superclass). This promotes code reuse.

List the parent class(es) in parentheses after the child class name.

class ParentClass:
parent_attr = 100
def parent_method(self):
print("Calling parent method")
# ChildClass inherits from ParentClass
class ChildClass(ParentClass):
def __init__(self):
# Optionally call parent's initializer
super().__init__() # Recommended way to call parent methods
print("Initializing ChildClass instance")
def child_method(self):
print("Calling child method")
# Create instance of ChildClass
child_instance = ChildClass()
# Access inherited attribute and method
print(child_instance.parent_attr) # Output: 100
child_instance.parent_method() # Output: Calling parent method
# Call child's own method
child_instance.child_method() # Output: Calling child method

A child class can provide its own implementation of a method inherited from the parent. This is called overriding.

class Parent:
def my_method(self):
print('Calling parent method')
class Child(Parent):
# Override my_method
def my_method(self):
print('Calling child method (override)')
# Optionally call the parent's version
super().my_method()
c = Child()
c.my_method()

Output:

Calling child method (override)
Calling parent method

Methods with double underscores at the beginning and end (e.g., __init__, __str__) provide special behaviors, often related to built-in functions or operators.

MethodDescription
__init__(self, ...)Constructor, called during object creation.
__str__(self)Returns informal string representation (used by print(), str()).
__repr__(self)Returns official/developer string representation (used by interpreter prompt, repr()).
__len__(self)Returns length (used by len()).
__del__(self)Destructor (called before garbage collection, use with caution).
__add__(self, other)Implements addition (+ operator).
__eq__(self, other)Implements equality comparison (== operator).

You can define how standard operators work with your custom objects by implementing the corresponding special methods.

Example: Vector class supporting addition:

class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
# Used by print()
return f'Vector({self.x}, {self.y})'
def __repr__(self):
# Used in interpreter output
return f'Vector({self.x!r}, {self.y!r})' # !r calls repr() on attributes
def __add__(self, other):
# Defines behavior for the '+' operator
if isinstance(other, Vector):
return Vector(self.x + other.x, self.y + other.y)
return NotImplemented # Allows Python to try other's __radd__
v1 = Vector(2, 10)
v2 = Vector(5, -2)
print(f"v1 = {v1}")
print(f"v2 = {v2}")
result_vector = v1 + v2
print(f"v1 + v2 = {result_vector}") # Uses __add__ and then __str__

Output:

v1 = Vector(2, 10)
v2 = Vector(5, -2)
v1 + v2 = Vector(7, 8)

While Python doesn’t have strict private members like Java or C++, it uses conventions:

  • Single underscore prefix (_attribute): Conventionally indicates internal use. Still accessible from outside but signals ‘don’t touch unless you know what you’re doing’.
  • Double underscore prefix (__attribute): Triggers ‘name mangling’. Python renames the attribute internally to _ClassName__attribute, making it harder (but not impossible) to access directly from outside.

Example:

class MyClass:
def __init__(self):
self.public_attr = 10
self._protected_attr = 20 # Convention: internal use
self.__private_attr = 30 # Name mangling occurs
def get_private(self):
return self.__private_attr
obj = MyClass()
print(obj.public_attr) # Accessible: 10
print(obj._protected_attr) # Accessible (by convention): 20
# print(obj.__private_attr) # AttributeError: 'MyClass' object has no attribute '__private_attr'
# Access via mangled name (discouraged)
print(obj._MyClass__private_attr) # Accessible: 30
print(obj.get_private()) # Access via public method: 30

Regular expressions (regex) are powerful tools for pattern matching in strings.

The re module in Python provides support for regular expressions.

  • re.match(pattern, string, flags=0): Tries to match the pattern only at the beginning of the string. Returns a match object on success, None otherwise.
  • re.search(pattern, string, flags=0): Scans through the string looking for the first location where the pattern matches. Returns a match object on success, None otherwise.
  • re.findall(pattern, string, flags=0): Finds all non-overlapping matches of the pattern in the string and returns them as a list of strings.
  • re.finditer(pattern, string, flags=0): Finds all non-overlapping matches and returns an iterator yielding match objects.
  • re.sub(pattern, replacement, string, count=0, flags=0): Replaces occurrences of the pattern in the string with the replacement. Returns the modified string.

Raw Strings (r"...") are highly recommended for regex patterns to avoid issues with backslash interpretation.

If match() or search() find a match, they return a match object with useful methods:

  • match.group(0) or match.group(): Returns the entire matched string.
  • match.group(num): Returns the string matched by subgroup num (parenthesized group in the pattern).
  • match.groups(): Returns a tuple containing strings for all subgroups.
  • match.start(): Returns the starting index of the match.
  • match.end(): Returns the ending index of the match.
  • match.span(): Returns a tuple (start, end).
import re
text = "The quick brown fox jumps over the lazy dog."
# Search for 'fox'
match = re.search(r"fox", text)
if match:
print(f"Found '{match.group()}' at index {match.start()}-{match.end()}")
else:
print("Pattern not found.")
# Find all words starting with 't'
matches = re.findall(r"\bt\w+", text, re.IGNORECASE) # \b=word boundary, \w+=one or more word chars
print(f"Words starting with 't': {matches}")
# Replace 'dog' with 'cat'
new_text = re.sub(r"dog", "cat", text)
print(f"Substituted text: {new_text}")
# Match at the beginning
match_start = re.match(r"The", text)
if match_start:
print("String starts with 'The'")
match_start_fail = re.match(r"quick", text) # Fails because 'quick' is not at the start
if not match_start_fail:
print("'quick' not found at the start.")

Output:

Found 'fox' at index 16-19
Words starting with 't': ['The', 'the']
Substituted text: The quick brown fox jumps over the lazy cat.
String starts with 'The'
'quick' not found at the start.
PatternDescription
.Matches any character except newline
^Matches the start of the string
$Matches the end of the string
*Matches 0 or more repetitions of the preceding element
+Matches 1 or more repetitions
?Matches 0 or 1 repetition
{n}Matches exactly n repetitions
{n,}Matches n or more repetitions
{n,m}Matches between n and m repetitions
[...]Character set (e.g., [abc], [a-z])
[^...]Negated character set
\dMatches any digit ([0-9])
\DMatches non-digit
\sMatches whitespace (space, tab, newline, etc.)
\SMatches non-whitespace
\wMatches word characters (letters, digits, underscore)
\WMatches non-word characters
\bWord boundary
(...)Capturing group
|Alternation (OR)
FlagDescription
re.IGNORECASE or re.ICase-insensitive matching
re.MULTILINE or re.M^ and $ match start/end of lines, not just the whole string
re.DOTALL or re.S. matches newline character as well