Overview

Python 3.3, released on September 29, 2012, brings features that deeply shape modern Python. The yield from syntax revolutionizes generator writing, the venv module integrates virtual environment creation directly into the standard library, and namespace packages finally allow spreading a package across multiple directories without an __init__.py file.

This version also introduces the faulthandler module, a valuable tool for diagnosing crashes related to segfaults and hangs. Python 3.3 confirms the growing maturity of the 3.x branch.

Major Features

yield from (PEP 380)

The yield from syntax allows a generator to delegate part of its operations to another generator. This greatly simplifies data pipelines and the refactoring of complex generators. Without yield from, you would need to manually iterate over the sub-generator with a for loop.

python
# Log file processing pipeline

def read_lines(path):
    """Generator that reads a file line by line."""
    with open(path) as f:
        for line in f:
            yield line.strip()

def filter_errors(lines):
    """Keep only lines containing ERROR."""
    for line in lines:
        if "ERROR" in line:
            yield line

def extract_messages(lines):
    """Extract the message after the log level."""
    for line in lines:
        parts = line.split(" - ", maxsplit=2)
        if len(parts) >= 3:
            yield parts[2]

def error_pipeline(paths):
    """Full pipeline: read multiple files and extract errors.

    Thanks to yield from, each step naturally delegates
    to the sub-generator without an explicit loop.
    """
    for path in paths:
        lines = read_lines(path)
        errors = filter_errors(lines)
        yield from extract_messages(errors)

# Usage
log_files = ["app.log", "worker.log", "api.log"]
for message in error_pipeline(log_files):
    print(f"[ERROR] {message}")

# Another example: flatten a nested structure
def flatten(structure):
    """Recursively flatten a nested structure."""
    for element in structure:
        if isinstance(element, (list, tuple)):
            yield from flatten(element)
        else:
            yield element

data = [1, [2, 3], [4, [5, 6]], 7]
print(list(flatten(data)))  # [1, 2, 3, 4, 5, 6, 7]

venv (PEP 405)

The venv module allows creating Python virtual environments directly from the standard library, without depending on the third-party virtualenv tool. Each environment has its own package directory, isolated from the system.

python
# Creating a virtual environment from the terminal
# python -m venv my_project_env

# Activation (Linux/macOS)
# source my_project_env/bin/activate

# Activation (Windows)
# my_project_env\Scripts\activate

# Checking that the environment is active
import sys
import os

def check_environment():
    """Display information about the active Python environment."""
    venv_path = os.environ.get("VIRTUAL_ENV")
    if venv_path:
        print(f"Active virtual environment: {venv_path}")
        print(f"Python executable: {sys.executable}")
        print(f"Package paths:")
        for path in sys.path:
            if "site-packages" in path:
                print(f"  {path}")
    else:
        print("No active virtual environment.")

check_environment()

# Programmatic environment creation
import venv

builder = venv.EnvBuilder(with_pip=True)
builder.create("test_env")
print("Environment 'test_env' created with bundled pip.")

Namespace packages (PEP 420)

Namespace packages allow creating Python packages spread across multiple directories, without needing an __init__.py file. This is particularly useful for large projects that distribute sub-packages in separate installations, such as plugins or extensions.

python
# Directory layout:
#
# directory_a/
#   myframework/
#     core/
#       __init__.py
#       engine.py
#
# directory_b/
#   myframework/
#     plugins/
#       __init__.py
#       csv_export.py
#
# No __init__.py in myframework/!
# Python 3.3 automatically merges both parts.

# In code, you import normally:
# import myframework.core.engine
# import myframework.plugins.csv_export

# Checking whether a package is a namespace package
import importlib

def inspect_package(name):
    """Display whether a package is a namespace package."""
    try:
        module = importlib.import_module(name)
        if hasattr(module, "__path__"):
            paths = list(module.__path__)
            if hasattr(module, "__file__") and module.__file__:
                print(f"{name}: regular package ({module.__file__})")
            else:
                print(f"{name}: namespace package")
                for path in paths:
                    print(f"  -> {path}")
        else:
            print(f"{name}: simple module")
    except ImportError:
        print(f"{name}: not found")

inspect_package("json")  # regular package
inspect_package("os")    # simple module

faulthandler

The faulthandler module displays the Python call stack when a fatal signal is received (segfault, bus error) or after a given timeout. It is an essential debugging tool for diagnosing silent crashes, especially those caused by faulty C extensions.

python
import faulthandler
import sys
import threading

# Enable faulthandler to capture segfaults
faulthandler.enable()

# Dump the call stack of all threads
# (useful for debugging a program that appears stuck)
def long_running_task():
    """Simulate a blocking task."""
    import time
    time.sleep(30)

thread = threading.Thread(target=long_running_task, name="BackgroundWork")
thread.daemon = True
thread.start()

# Display the state of all threads
faulthandler.dump_traceback(file=sys.stderr, all_threads=True)

# Trigger a dump after a delay (watchdog)
# If the program does not respond within 10 seconds,
# the call stack will be printed automatically.
faulthandler.dump_traceback_later(
    timeout=10,
    repeat=False,
    file=sys.stderr,
)

# You can also enable it via the command line:
# python -X faulthandler my_script.py
#
# Or via an environment variable:
# PYTHONFAULTHANDLER=1 python my_script.py

# Cancel the watchdog if everything went fine
faulthandler.cancel_dump_traceback_later()

Minor Improvements

  • Hash randomization is enabled by default, improving security against hash collision attacks.
  • The new unittest.mock module makes unit testing with mocks much easier.
  • The decimal module is rewritten in C for up to 120x faster performance.
  • The internal string representation uses flexible storage (PEP 393), reducing memory consumption.
  • The raise ... from None keyword allows suppressing exception chaining.

Deprecations and Removals

Notable changes:

  • The u"..." string prefix is restored to ease migration from Python 2 (PEP 414).
  • The packaging support in distutils begins to show its age.
  • time.clock() is deprecated in favor of time.perf_counter() and time.process_time().

Sources