>Python: Intermediate 2

Contents

Modules & Packages: __init__.py, Relative Imports, __main__

Learn how to organize code into modules and packages, use __init__.py, perform relative imports, and understand the __main__ entry point.

# mymodule.py
def greet(name):
    print(f"Hello, {name}!")

# Using __name__ and __main__
if __name__ == "__main__":
    greet("PySank")

# Directory structure for a package:
# mypackage/
#   __init__.py
#   module1.py
#   module2.py

# __init__.py can be empty or set up package exports
# module1.py
def foo():
    return "foo"

# module2.py
from .module1 import foo  # relative import

# Importing from a package
import mypackage.module1
from mypackage import module2

# Dynamic imports
import importlib
math = importlib.import_module("math")
print(math.sqrt(16))

OOP in Python

Explore classes, inheritance, encapsulation, polymorphism, and more in Python's object-oriented programming model.

# Basic class
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        return f"{self.name} makes a sound."

# Inheritance
class Dog(Animal):
    def speak(self):
        return f"{self.name} barks."

# Encapsulation
class Counter:
    def __init__(self):
        self._count = 0  # protected
        self.__secret = 42  # private
    def increment(self):
        self._count += 1
    def get_secret(self):
        return self.__secret

# Polymorphism
def animal_sound(animal):
    print(animal.speak())

animal_sound(Animal("Generic"))
animal_sound(Dog("Buddy"))

# Multiple inheritance
class Flyer:
    def fly(self):
        return "Flying!"
class Bird(Animal, Flyer):
    pass
bird = Bird("Parrot")
print(bird.speak(), bird.fly())

Dunder Methods: __str__, __repr__, __len__, __call__

Special methods ("dunder" = double underscore) let you customize class behavior for built-ins like str(), len(), and more.

class Book:
    def __init__(self, title, pages):
        self.title = title
        self.pages = pages
    def __str__(self):
        return f"Book: {self.title}"
    def __repr__(self):
        return f"Book({self.title!r}, {self.pages!r})"
    def __len__(self):
        return self.pages
    def __call__(self, reader):
        return f"{reader} is reading {self.title}"

b = Book("Python 101", 350)
print(str(b))      # Book: Python 101
print(repr(b))     # Book('Python 101', 350)
print(len(b))      # 350
print(b("Alice"))  # Alice is reading Python 101

# More dunder methods
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    def __repr__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)     # Vector(4, 6)
print(v1 == v2)    # False

Type Hinting (typing module: List, Dict, Union, Optional)

Use type hints to improve code clarity and catch bugs early. The typing module provides advanced types.

from typing import List, Dict, Union, Optional

def add_numbers(a: int, b: int) -> int:
    return a + b

def process_items(items: List[str]) -> None:
    for item in items:
        print(item)

def get_user(id: int) -> Optional[Dict[str, Union[str, int]]]:
    if id == 1:
        return {"name": "Alice", "age": 30}
    return None

# Type hints for variables (Python 3.6+)
name: str = "Bob"
scores: List[int] = [90, 80, 70]

# Type checking with mypy (install separately)
# mypy script.py

File Handling: Text & Binary Files, with open()

Read and write text and binary files safely using with open() context manager.

# Writing text
with open("example.txt", "w", encoding="utf-8") as f:
    f.write("Hello, world!\n")
    f.write("Another line\n")

# Reading text
with open("example.txt", "r", encoding="utf-8") as f:
    for line in f:
        print(line.strip())

# Appending
with open("example.txt", "a", encoding="utf-8") as f:
    f.write("Appended line\n")

# Binary files
with open("image.png", "rb") as f:
    data = f.read(10)
    print(data)

# Exception handling
try:
    with open("notfound.txt") as f:
        f.read()
except FileNotFoundError:
    print("File not found!")

Exception Hierarchy & Custom Exceptions

Understand Python's exception hierarchy and how to define your own custom exceptions.

# Built-in exception hierarchy
try:
    1 / 0
except ZeroDivisionError as e:
    print("Caught:", e)

# Custom exception
class MyError(Exception):
    pass

def risky():
    raise MyError("Something went wrong!")

try:
    risky()
except MyError as e:
    print("Custom exception:", e)

# Exception chaining
try:
    try:
        int("abc")
    except ValueError as e:
        raise RuntimeError("Failed to parse!") from e
except RuntimeError as e:
    print("Chained exception:", e)