This chapter covers advanced Python features. Several topics have dedicated deep-dive pages — follow the links for comprehensive coverage.

Topic Chapter
Iterators & Generators Iterators & Generators
Decorators Decorators
Regular Expressions Regular Expressions
Databases Working with Databases
Type Hints Type Hints
Async Async Programming
Metaprogramming Metaprogramming

Context Managers

Context managers guarantee setup and cleanup — most commonly for files, locks, and database connections:

  with open("data.txt") as f:
    content = f.read()
# File automatically closed, even if an exception occurred
  

Custom Context Managers

Using a class:

  class Timer:
    def __enter__(self):
        import time
        self.start = time.perf_counter()
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        elapsed = time.perf_counter() - self.start
        print(f"Elapsed: {elapsed:.4f}s")
        return False  # don't suppress exceptions

with Timer():
    sum(range(1_000_000))
  

Using @contextmanager:

  from contextlib import contextmanager

@contextmanager
def temporary_file(content):
    import tempfile, os
    path = tempfile.mktemp(suffix=".txt")
    with open(path, "w") as f:
        f.write(content)
    try:
        yield path
    finally:
        os.remove(path)

with temporary_file("test data") as path:
    with open(path) as f:
        print(f.read())
  

contextlib Utilities

  from contextlib import suppress, redirect_stdout
import io

# Suppress specific exceptions
with suppress(FileNotFoundError):
    os.remove("nonexistent.txt")

# Capture stdout
buffer = io.StringIO()
with redirect_stdout(buffer):
    print("captured")
print(buffer.getvalue())  # "captured\n"
  

Memory Management

Python uses reference counting plus a generational garbage collector for cycles:

  import sys

a = [1, 2, 3]
print(sys.getrefcount(a))  # reference count (includes temp ref from getrefcount)

del a  # decrements count; memory freed when count hits 0
  

Weak References

  import weakref

class Cache:
    def __init__(self):
        self._store = {}

    def get(self, key, factory):
        obj = self._store.get(key)
        if obj is None or obj() is None:
            instance = factory()
            self._store[key] = weakref.ref(instance)
            return instance
        return obj()
  

__slots__ for Memory Efficiency

  class Point:
    __slots__ = ("x", "y")
    def __init__(self, x, y):
        self.x = x
        self.y = y
  

Instances use ~40% less memory than regular classes — useful when creating millions of objects.

The dataclasses Module

Reduce boilerplate for data-holding classes:

  from dataclasses import dataclass, field
from typing import list

@dataclass
class Product:
    name: str
    price: float
    tags: list[str] = field(default_factory=list)

    @property
    def display_price(self):
        return f"${self.price:.2f}"

p = Product("Widget", 9.99, tags=["sale"])
print(p)  # Product(name='Widget', price=9.99, tags=['sale'])
  

@dataclass auto-generates __init__, __repr__, __eq__, and more.

The enum Module

Define named constants:

  from enum import Enum, auto

class Status(Enum):
    PENDING = "pending"
    ACTIVE = "active"
    CLOSED = "closed"

class Color(Enum):
    RED = auto()
    GREEN = auto()
    BLUE = auto()

print(Status.ACTIVE)       # Status.ACTIVE
print(Status.ACTIVE.value) # "active"
  

Walrus Operator (:=)

Assign and use a value in one expression (Python 3.8+):

  # Before
data = read_file()
if data:
    process(data)

# With walrus
if (data := read_file()):
    process(data)

# In comprehensions
results = [y for x in inputs if (y := transform(x)) is not None]
  

These advanced features unlock cleaner, more efficient Python code. Explore the linked chapters for full coverage of each topic.