Theoretical Python Tutorial

Complete Guide from Beginner to Professional Level (2025 Edition)

0. Python Fundamentals and Core Concepts

Understanding the processing nature and core structure of Python code is essential.

Processing Model: Interpreted and Bytecode


# Python Execution Flow:
# 1. Source Code (my_script.py) -> 
# 2. Compiler -> 
# 3. Bytecode (my_script.pyc) -> 
# 4. Python Virtual Machine (PVM) -> 
# 5. Machine Code Execution
                

Core Terminology: Statement, Expression, Variable, and Value

Memory and Capacity: Bits and Arbitrary Precision


1. Input, Output, and Essential Coding Rules

Mastering basic I/O and strictly adhering to syntax rules are the first steps in programming.

1.1. Core I/O Functions: print() and input()

1.2. Strict Coding Requirements and Rules

1.3. Fundamental Limitations and Constraints


2. Core Python Terminology

  1. Python: High-level, interpreted, general-purpose programming language known for readability.
  2. Interpreter: Executes Python code line-by-line without prior compilation.
  3. Variable: Named storage location that holds a value (dynamic typing).
  4. Data Type: Classification of data (int, float, str, bool, list, tuple, dict, set).
  5. Function: Reusable block of code that performs a specific task.
  6. Module: File containing Python definitions and statements (.py).
  7. Package: Collection of modules in directories with __init__.py.
  8. Class: Blueprint for creating objects (OOP).
  9. Object: Instance of a class.
  10. Exception: Runtime error that disrupts normal flow.
  11. PIP: Package installer for Python (pip install package_name).
  12. IDE: Integrated Development Environment (VS Code, PyCharm, etc.).
  13. REPL: Read-Eval-Print Loop (interactive Python shell).
  14. Virtual Environment: Isolated Python environment (venv, virtualenv).
  15. PEP 8: Official Python style guide.
  16. LEGB Rule: Rule determining the order in which Python searches for variable names (Local, Enclosing, Global, Built-in).

3. Operator Precedence in Python (Highest to Lowest)

  1. () – Parentheses (Grouping)
  2. ** – Exponentiation
  3. +x, -x, ~x – Unary operations (Positive, Negative, Bitwise NOT)
  4. * , / , // , % – Multiplication, division, floor division, modulo
  5. + , - – Addition, subtraction
  6. << , >> – Bitwise shifts (Left Shift, Right Shift)
  7. & – Bitwise AND
  8. ^ – Bitwise XOR
  9. | – Bitwise OR
  10. ==, !=, <, <=, >, >=, is, is not, in, not in – Comparisons, identity, membership
  11. not – Logical NOT (Negation)
  12. and – Logical AND
  13. or – Logical OR

Example:


# Precedence ensures that ** is calculated before * and /, and they are calculated before +
result = 2 + 3 * 4 ** 2 / (5 - 1)
# 1. (5 - 1) -> 4  (Parentheses)
# 2. 4 ** 2 -> 16  (Exponentiation)
# 3. 3 * 16 -> 48  (Multiplication/Division - left to right)
# 4. 48 / 4 -> 12.0 (Multiplication/Division - left to right)
# 5. 2 + 12.0 -> 14.0 (Addition/Subtraction)
print(result)  # Output: 14.0
                

3.1. Boolean Rules and Truthiness

The bool type represents truth values (True or False).

Logical Operations

Truthiness (Implicit Boolean Conversion)

Every object in Python has an implicit boolean value (Truthiness). Objects that evaluate to False are called "Falsy" values:


# Truthiness in action:
if "hello": 
    print("String is Truthy") # Executes, because "hello" is True
    
# Short-circuiting with 'and' and 'or'
result_and = 0 and "data" # 0 is Falsy, so it returns 0 (short-circuits)
result_or = 1 or "data"  # 1 is Truthy, so it returns 1 (short-circuits)
print(f"AND result: {result_and}") # Output: 0
print(f"OR result: {result_or}")   # Output: 1
                

4. Variables and Core Data Types

Python has dynamic typing (you don't declare the type), but variables still hold typed objects.

Mutable vs Immutable

Mutability determines if an object can change its state after creation. Changes to immutable objects result in a new memory address (new object).

Example: Mutability in Action


x = 10          # int (immutable)
y = [1, 2, 3]   # list (mutable)

# List modification (in place)
y.append(4)     # modifies the list without changing its ID (usually)
print(y)        # [1, 2, 3, 4]

# Tuple modification (requires new object)
a = (1, 2)
# a[0] = 5     # ERROR: This would raise a TypeError (immutable)
                

4.1. Rules and Methods for Collections (List, Tuple, Dict)

List (Mutable, Ordered, Indexed)

Tuple (Immutable, Ordered, Indexed)

Dictionary (Mutable, Unordered Pre-3.7/Ordered 3.7+, Key-Value Mapping)

4.2. Advanced Collection Operations: Slicing and Manipulation

Slicing in Python (List, Tuple, String)

Slicing is a powerful syntax used for extracting a part (a subsequence) of an ordered collection using indices. It always returns a new collection of the same type (a new list, a new tuple, or a new string).

General Syntax: [start:stop:step]

Example: List and Tuple Slicing


data_list = [10, 20, 30, 40, 50, 60]
data_tuple = (1, 2, 3, 4, 5, 6, 7)
my_string = "Cybersecurity"

# Basic Slicing: [1:4] gets elements from index 1 up to (but not including) 4
print(f"List [1:4]: {data_list[1:4]}")     # Output: [20, 30, 40]
print(f"Tuple [3:]: {data_tuple[3:]}")     # Output: (4, 5, 6, 7) (from index 3 to end)
print(f"String [:5]: {my_string[:5]}")     # Output: 'Cyber' (from start up to index 5)

# Negative Indices: count from the end
print(f"List [-3:]: {data_list[-3:]}")    # Output: [40, 50, 60] (last 3 elements)

# Step/Stride: [::2] takes every second element
print(f"List [::2]: {data_list[::2]}")    # Output: [10, 30, 50]

# Reversing a Collection (using a step of -1)
print(f"Tuple Reversed: {data_tuple[::-1]}") # Output: (7, 6, 5, 4, 3, 2, 1)

# Slicing Assignment (List only)
# Assigning a new list to a slice (this mutates the list)
data_list[1:3] = [25, 35] # Replace [20, 30] with [25, 35]
print(f"List After Slice Assignment: {data_list}") # Output: [10, 25, 35, 40, 50, 60]
                

Complete Collection Manipulation Examples (Add, Remove, Delete)

List Manipulation


my_list = ['A', 'B', 'C', 'D']
print(f"Initial List: {my_list}")

# 1. ADDING
my_list.append('E')       # Appends a single item
my_list.insert(1, 'X')    # Inserts 'X' at index 1
my_list.extend(['F', 'G'])# Extends with an iterable
print(f"After Add: {my_list}") # Output: ['A', 'X', 'B', 'C', 'D', 'E', 'F', 'G']

# 2. REMOVING / DELETING
value_removed = my_list.pop(2) # Removes and returns item at index 2 ('B')
print(f"Popped Item: {value_removed}")
my_list.remove('D')            # Removes the value 'D' (first occurrence)

del my_list[4]                 # Deletes item at index 4 ('F')
del my_list[2:4]               # Deletes a slice ('C', 'E')
print(f"After Remove/Delete: {my_list}") # Output: ['A', 'X', 'G']
                

Tuple Manipulation (New Object Creation)


my_tuple = (1, 2, 3)
print(f"Initial Tuple: {my_tuple}")

# 1. ADDING (Must create a new tuple)
new_tuple = my_tuple + (4, 5) # Concatenation creates a new tuple (1, 2, 3, 4, 5)
print(f"After 'Add' (New Object): {new_tuple}")

# 2. REMOVING (Must create a new tuple without the desired element)
# Example: Create a new tuple without element 2
index_to_remove = 1 
removed_tuple = my_tuple[:index_to_remove] + my_tuple[index_to_remove + 1:]
print(f"After 'Remove' (New Object): {removed_tuple}") # Output: (1, 3)

# 3. DELETING the tuple variable itself
# del my_tuple # This removes the 'my_tuple' name from memory
                

Dictionary Manipulation


my_dict = {'a': 10, 'b': 20, 'c': 30, 'd': 40}
print(f"Initial Dict: {my_dict}")

# 1. ADDING / UPDATING
my_dict['e'] = 50           # Add new key 'e'
my_dict['a'] = 15           # Update existing key 'a'
my_dict.update({'f': 60, 'g': 70}) # Add multiple items via update
print(f"After Add/Update: {my_dict}") # Output: {'a': 15, 'b': 20, 'c': 30, 'd': 40, 'e': 50, 'f': 60, 'g': 70}

# 2. REMOVING / DELETING
popped_val = my_dict.pop('c') # Removes 'c' key and returns its value (30)
print(f"Popped Value: {popped_val}")
del my_dict['b']              # Deletes the key 'b'
print(f"After Remove/Delete: {my_dict}") # Output: {'a': 15, 'd': 40, 'e': 50, 'f': 60, 'g': 70}
                

5. Control Flow Statements

Statements that dictate the sequence of code execution.

Example: Loop Control Keywords


# Loop from 1 to 9
for i in range(1, 10):
    if i % 2 == 0:
        continue # Skip even numbers (2, 4, 6, 8)
    
    if i == 7:
        break    # Stop iteration when 7 is reached
        
    print(i)  # Prints 1, 3, 5

# Example of match-case (Python 3.10+)
status_code = 404
match status_code:
    case 200:
        print("OK")
    case 404:
        print("Not Found")
    case _: # Default case
        print("Unknown")
                

6. Functions (def and lambda)

Functions are callable units that encapsulate a specific task.

Function Definition (def)

Used for standard, multi-line, named functions. Supports documentation and type hinting.


def greet(name: str, age: int = 25) -> str:
    """
    Return a greeting message. Uses type hints for clarity.
    :param name: The person's name (str).
    :param age: The person's age (int, default is 25).
    :return: A formatted greeting string (str).
    """
    return f"Hello {name}, you are {age} years old."

# Calling the function with and without the optional argument
message1 = greet("Mir Ali Shahidi", 39)
message2 = greet("A student")
print(message1) # Output: Hello Mir Ali Shahidi, you are 39 years old.
print(message2) # Output: Hello A student, you are 25 years old.
                

Anonymous Function (lambda)

Used for small, single-expression functions. Cannot contain multiple statements.


# Lambda is restricted to a single expression
square = lambda x: x ** 2
print(f"5 squared is: {square(5)}") # Output: 5 squared is: 25

# Used often for sorting based on a complex key
points = [(1, 2), (0, 5), (3, 1)]
# Sort by the second element (index 1) of the tuple
sorted_by_y = sorted(points, key=lambda p: p[1])
print(f"Sorted by Y-axis: {sorted_by_y}") # Output: [(3, 1), (1, 2), (0, 5)]
                

6.1. Variable Scope (LEGB Rule)

The LEGB Rule defines the order Python looks for names (variables, functions, etc.) in a nested structure:


global_var = 10 # G (Global)

def outer_func():
    enclosing_var = 20 # E (Enclosing)

    def inner_func():
        local_var = 30 # L (Local)
        # Accessing variables in LEGB order
        print(f"L: {local_var}") 
        print(f"E: {enclosing_var}")
        print(f"G: {global_var}")
        # Built-in is len()
        print(f"B: {len([1, 2])}") 
    
    inner_func()

outer_func()
                

7. Object-Oriented Programming (OOP)

A programming paradigm centered around classes and objects.

Example: Class, Object, Attributes, and Methods


class Person:
    species = "Homo sapiens"  # Class attribute (shared by all instances)

    def __init__(self, name: str, age: int):
        """Constructor: Initializes a new object with instance attributes."""
        self.name = name      # Instance attribute
        self.age = age

    def introduce(self):
        """Instance method: operates on the object's data (self.name, self.age)."""
        return f"Hi, I'm {self.name} and I am {self.age}. We are all {self.species}."

# Creating an object (Instance)
p = Person("Alice", 30)
print(p.introduce()) 
print(Person.species) # Accessing Class Attribute
                

Key OOP Principles

7.1. Special Methods (Dunder Methods)

Methods surrounded by double underscores (e.g., __init__) are called Dunder Methods or Magic Methods. They define how objects interact with Python's core language features (operators, functions, built-ins).


class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    # Defines behavior for the + operator (operator overloading)
    def __add__(self, other): 
        return Vector(self.x + other.x, self.y + other.y)

    # Defines the official string representation (used by repr() or debugging)
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"
    
    # Defines behavior for the len() built-in function
    def __len__(self):
        return 2 # A vector always has x and y components

v1 = Vector(1, 2)
v2 = Vector(3, 4)
v3 = v1 + v2 # Calls v1.__add__(v2)
print(v3)    # Output: Vector(4, 6) (Calls __repr__)
print(len(v3)) # Output: 2 (Calls __len__)
                

7.2. Class Methods vs. Static Methods


8. Frequently Used Built-in Methods & Functions

These methods are essential for daily data manipulation across different types.

8.1. Built-in Functions (Essential)

These are always available without needing to import a module.


data = [3, 1, 4, 2]
print(f"Sorted (New List): {sorted(data)}") # [1, 2, 3, 4]
print(f"Original Data: {data}") # [3, 1, 4, 2] (No change)
                

8.2. String Methods


text = " Python is awesome "
# Strings are immutable; methods return a new string
print(f".upper(): {text.upper()}")     # Output: ' PYTHON IS AWESOME '
print(f".strip(): {text.strip()}")     # Output: 'Python is awesome' (removes leading/trailing whitespace)
print(f".split(): {text.strip().split()}") # Output: ['Python', 'is', 'awesome']
print(f".join(): {'-'.join(['A', 'B', 'C'])}") # Output: 'A-B-C' (joins elements of an iterable)
print(f".replace(): {text.replace('Python', 'Java')}") # Output: ' Java is awesome '
print(f".startswith(): {text.strip().startswith('P')}") # Output: True
                

8.3. List Methods and Functions (See also 4.1)


data = [10, 50, 20]
data.append(40)     # Adds 40 to the end
data.insert(1, 15)  # Inserts 15 at index 1 -> [10, 15, 50, 20, 40]
other = [30, 60]
data.extend(other)  # Merges 'other' into 'data'
print(f"Extended List: {data}") # [10, 15, 50, 20, 40, 30, 60]

data.pop(2)         # Removes and returns element at index 2 (50 is removed)
data.remove(20)     # Removes the first occurrence of the VALUE 20
data.sort()         # Sorts the list in place (mutates the list)
print(f"Final Sorted List: {data}") # [10, 15, 30, 40, 60]
print(f"Length (len()): {len(data)}") # Output: 5 (len() is a built-in function, not a method)
                

8.4. Dictionary Methods (See also 4.1)


user = {'id': 101, 'name': 'Alice', 'role': 'Admin'}
# .get() provides safe access, returning default if key is missing
print(f"Value for 'role': {user.get('role', 'Guest')}") # Output: Admin
print(f"Value for 'email' (safe): {user.get('email', 'N/A')}") # Output: N/A

user.update({'role': 'Manager', 'city': 'NY'}) # Updates existing and adds new keys
print(f"Keys View: {user.keys()}") # Returns a dynamic view object
print(f"Items View: {user.items()}") 

removed_role = user.pop('role') # Removes the key and returns its value
print(f"Removed Item: {removed_role}") # Output: Manager
                

8.5. Set Methods


A = {1, 2, 3, 4}
B = {3, 4, 5, 6}

A.add(5) # Adds 5 to set A
A.remove(1) # Removes 1 from set A. Raises KeyError if 1 is not present.
print(f"Union (A | B): {A.union(B)}") # {2, 3, 4, 5, 6} (Elements in A or B)
print(f"Intersection (A & B): {A.intersection(B)}") # {3, 4, 5} (Elements common to A and B)
print(f"Difference (A - B): {A.difference(B)}") # {2} (Elements in A but not B)
                

9. Intermediate Python Concepts

Mastering these concepts allows for more efficient, Pythonic, and robust code.


10. Advanced Python Concepts

These topics are crucial for writing enterprise-level, maintainable, and highly flexible code.

Decorators

A Decorator is a function that takes another function and extends its behavior without modifying the original function's source code. This is used for logging, timing, authorization, and caching. The @ syntax is syntactic sugar for function = decorator(function).


def log_execution(func):
    """A simple decorator to log function calls."""
    def wrapper(*args, **kwargs):
        print(f"--- Calling {func.__name__} with arguments: {args} ---")
        result = func(*args, **kwargs)
        print(f"--- {func.__name__} returned: {result} ---")
        return result
    return wrapper

@log_execution
def add(a, b):
    return a + b

add(10, 5)
# Output:
# --- Calling add with arguments: (10, 5) ---
# --- add returned: 15 ---
                

Async/Await (Concurrency)

Keywords async (defines a coroutine) and await (pauses a coroutine non-blockingly) are central to the asyncio framework. This enables efficient handling of numerous I/O-bound tasks (network requests, database queries) concurrently on a single thread. This is different from parallelism (multithreading/multiprocessing).


import asyncio
import time

async def fetch_data(delay):
    """A coroutine simulating an I/O delay."""
    print(f"Starting fetch data after {delay}s...")
    await asyncio.sleep(delay) # Non-blocking wait: Python can switch to another task
    return f"Data fetched after {delay} seconds."

async def run_concurrent():
    start = time.time()
    # Execute multiple tasks concurrently, waiting for all to complete
    results = await asyncio.gather(
        fetch_data(3), # Longest task
        fetch_data(1)  # Shortest task
    )
    # The total time is ~3 seconds, as they run at the same time.
    print(f"Results: {results}") 
    print(f"Total time: {time.time() - start:.2f}s") 

# This must be run outside this block using: asyncio.run(run_concurrent())
                

Metaclasses

The "class of a class." A Metaclass inherits from type and controls the class creation process (the mechanism that reads the class definition and creates the class object). They are highly advanced tools used in frameworks to enforce coding standards, auto-register classes, or inject methods dynamically.


class SecureAttributeMeta(type):
    """Metaclass that automatically adds an 'audit_log' list to every class created."""
    def __new__(mcs, name, bases, attrs):
        # Enforce rule: All new classes must have a logging mechanism
        if 'audit_log' not in attrs:
            attrs['audit_log'] = []
        
        # Call the standard type creation process
        return super().__new__(mcs, name, bases, attrs)

class DatabaseConnector(metaclass=SecureAttributeMeta):
    # This class automatically inherits the 'audit_log' attribute
    pass

print(f"Connector has log: {DatabaseConnector.audit_log}") # Output: []
DatabaseConnector.audit_log.append("Connection successful")
print(f"Log updated: {DatabaseConnector.audit_log}") # Output: ['Connection successful']
                

11. Professional Practices: Error Handling & Quality

Writing production-ready code requires disciplined error handling and adherence to standards.

Exception Handling Deep Dive (try/except/else/finally)

Use the full structure for robust error management and guaranteed resource cleanup.


file = None
try:
    # 1. Try: Code that might raise an exception
    # file = open("non_existent_file.txt", "r") # Example: Raises FileNotFoundError
    result = 10 / 0 # Raises ZeroDivisionError
    
except FileNotFoundError:
    # 2. Except: Handle specific known error
    print("Handled: The file was not found.")
    
except ZeroDivisionError:
    # 3. Except: Handle specific known error
    print("Handled: Cannot divide by zero.")
    
except Exception as e:
    # 4. Except (Catch-all): Catch all other unexpected errors
    print(f"Caught unexpected error: {e}")
    
else:
    # 5. Else: Executes ONLY if the try block completes WITHOUT any exception
    print("Success: No errors occurred.")
    
finally:
    # 6. Finally: ALWAYS executes, regardless of success or failure (Essential for cleanup)
    if file:
        file.close()
    print("Resource cleanup finished.")
                

11.1. Raising Custom Exceptions

For professional code, define and raise specific exception classes to make error handling clearer for the caller.


class SecurityError(Exception):
    """Custom exception for security-related issues."""
    pass

def check_permission(user_role):
    if user_role != "Admin":
        raise SecurityError(f"User {user_role} is unauthorized.")
    print("Permission granted.")

try:
    check_permission("Guest")
except SecurityError as e:
    print(f"Access Denied: {e}") 
# Output: Access Denied: User Guest is unauthorized.
                

Documentation and Type Hinting

Docstrings (PEP 257)

Use triple quotes ("""...""") to document modules, functions, classes, and methods. PEP 257 defines the conventions for writing effective docstrings, typically covering parameters (Args), return value (Returns), and possible exceptions (Raises).


def analyze_network_traffic(data: bytes, threshold: int) -> bool:
    """
    Analyzes network data for malicious activity exceeding a threshold.

    Args:
        data: The raw network packet data (bytes).
        threshold: The maximum acceptable threat level score (int).

    Returns:
        True if the threat level is acceptable, False otherwise (bool).

    Raises:
        ValueError: If data is empty.
    """
    if not data:
        raise ValueError("Input data cannot be empty.")
    # Implementation logic...
    return True

# help(analyze_network_traffic) will display this documentation.
                

Type Hinting (PEP 484)

Annotations (param: type -> return_type) that specify the expected data types. This is purely for static analysis (tools like Mypy) and IDE assistance; Python does not enforce them at runtime.


from typing import List, Dict, Optional, Union

# Optional: may be T or None / Union: may be one of the specified types
def get_user_config(user_id: int) -> Optional[Dict[str, Union[str, int]]]:
    """Returns user config dictionary or None if not found."""
    if user_id == 1:
        return {"name": "Mirali", "level": 5}
    return None

config = get_user_config(1)
print(f"Config is: {config}") # Static analysis ensures config is handled correctly
                

Testing (Unit Testing & TDD)

Unit Testing (using unittest)

Ensuring that the smallest testable parts of an application (units) are individually correct. The unittest module provides the foundational framework.


import unittest

def is_secure_password(password):
    """Checks if a password is secure (length > 8)."""
    return len(password) > 8

class TestSecurity(unittest.TestCase):
    
    # Test method for a successful case
    def test_long_password(self):
        self.assertTrue(is_secure_password("a_strong_password_123"))

    # Test method for a failure case
    def test_short_password(self):
        # Checks if the result is False
        self.assertFalse(is_secure_password("short"))

# To run tests from the command line: python -m unittest filename.py
                

TDD (Test-Driven Development)

A software development process where development cycles are driven by writing failing tests before writing the necessary code to make those tests pass. This ensures high test coverage and robust design from the start.


12. Professional Python Ecosystem and Tools

Mastering these tools is essential for working in team environments and large-scale projects.

12.1. Environment and Dependency Management

12.2. Standard Library Modules (Essentials)

These modules are included with Python and must be known for professional scripting.

12.3. Importing Modules and Packages

Rules for bringing external code into your current file:


13. Complete File Handling and I/O Operations

Efficient and secure file input/output (I/O) is crucial for data persistence and logging. Using the with open(...) statement is the professional standard as it guarantees the file is closed, even if errors occur.

13.1. Fundamental File Modes

The open(filename, mode) function uses specific modes to define how the file is accessed:

13.2. Writing and Overwriting Data ('w' and 'w+')

Using the 'w' mode completely clears the file before writing the new content.

Example: Creating and Overwriting a File


filename = "security_report.txt"
content_to_write = [
    "--- System Audit Log ---",
    "Status: Clean",
    "Time: 2025-11-25 12:00:00"
]

# Use 'w' mode to overwrite existing content
with open(filename, 'w') as f:
    f.write("Initial write: This will be replaced.\n")
    f.writelines(line + "\n" for line in content_to_write)
    print(f"File '{filename}' created and overwritten.")

# The file now contains the content_to_write list.
                

13.3. Reading Data from a File ('r')

The 'r' mode is used to retrieve content from a file.

Example: Reading Methods


# Re-using the file created above
read_filename = "security_report.txt"

# Method 1: Reading the entire file content as a single string
with open(read_filename, 'r') as f:
    full_content = f.read()
    print("--- Full Content (read()) ---")
    print(full_content)

# Method 2: Reading content line by line (most memory-efficient for large files)
with open(read_filename, 'r') as f:
    print("--- Reading Line by Line (readlines()) ---")
    lines = f.readlines()
    for i, line in enumerate(lines):
        print(f"Line {i}: {line.strip()}") 
        
# Method 3: Iterating directly over the file object (the best way)
print("--- Iterating Directly ---")
with open(read_filename, 'r') as f:
    for line in f:
        if "Status" in line:
            print(f"Found status line: {line.strip()}")
                

13.4. Appending and Modifying ('a' and 'r+')

The 'a' (append) mode adds new data to the end of the file without deleting the existing content. The 'r+' mode allows reading and writing, keeping the original content.

Example: Appending Data ('a')


append_filename = "security_report.txt"

# Use 'a' mode to append to the end
with open(append_filename, 'a') as f:
    f.write("\nNew entry: Integrity Check Passed.")
    print(f"Appended a new line to '{append_filename}'.")
    
# Reading the appended content
with open(append_filename, 'r') as f:
    print("--- Appended Content Check ---")
    print(f.read())
                

13.5. Pointer Management (Seek and Tell)

When working with files in read/write modes, the file pointer (cursor) dictates the next position for I/O. seek() moves the pointer, and tell() reports its current position.

Example: Rewriting a specific line ('r+')


pointer_filename = "data_file.txt"
# Create dummy file with 'w'
with open(pointer_filename, 'w') as f:
    f.write("Line 1: Old Data\nLine 2: Target Data\nLine 3: Final Data")

# Open in 'r+' mode (Read/Write, pointer starts at 0)
with open(pointer_filename, 'r+') as f:
    # Read the first line to move the pointer
    first_line = f.readline()
    print(f"Current Position (after reading Line 1): {f.tell()}") # e.g., 18

    # Use seek to go back 5 characters (to overwrite 'Old Data' with 'New Data')
    # Since we are in text mode, seek is complicated, but for simplicity:
    f.seek(10) # Manually moving pointer back to start of 'Old'
    
    # Overwrite the data from the current position
    f.write("Updated Data") # Overwrites 'Target Data' and moves pointer
    
    # Move pointer to the beginning to read the new content
    f.seek(0)
    print("--- Content After Seek and Write ---")
    print(f.read()) 
# Output: Line 1: Updated Data\nLine 3: Final Data (Note: The length of the new string matters in r+!)
                

13.6. Binary File Operations

When dealing with non-text files (images, compressed files), the 'b' mode is mandatory. I/O operations must use bytes objects instead of str.

Example: Writing Binary Data ('wb')


binary_filename = "binary_log.bin"
binary_data = b'\x48\x65\x6C\x6C\x6F' # ASCII for "Hello" in bytes

with open(binary_filename, 'wb') as f:
    f.write(binary_data)
    print(f"Wrote {len(binary_data)} bytes to '{binary_filename}'.")

# Reading binary data ('rb')
with open(binary_filename, 'rb') as f:
    data = f.read()
    print(f"Read binary data: {data}") # Output: b'Hello'
                

Happy Coding! Keep practicing and building projects. For professional consulting, contact Mir Ali Shahidi via miralishahidi.ir.