W3docs

Python-Pakete und das Import-System

So funktionieren Python-Pakete: Paket mit __init__.py erstellen, absolute und relative Imports verwenden, eine saubere API definieren und Fallstricke vermeiden.

Ein Paket ist ein Verzeichnis mit Python-Modulen, das als eine einzige importierbare Einheit behandelt wird. Während ein Modul aus einer einzigen .py-Datei besteht, ist ein Paket ein Ordner — der potenziell viele Module und Unterpakete enthält — durch den das Import-System von Python wie durch einen Baum navigieren kann. Dieses Kapitel erklärt, wie man Pakete erstellt, steuert, was sie nach außen freigeben, absolute und relative Imports korrekt schreibt und die Fallstricke vermeidet, über die Einsteiger stolpern.

Module vs. Pakete — der wesentliche Unterschied

Ein Modul ist eine einzelne .py-Datei:

greetings.py        ← module

Ein Paket ist ein Verzeichnis, das mindestens eine spezielle Datei namens __init__.py enthält:

greetings/          ← package
    __init__.py
    english.py
    spanish.py

Beide werden mit dem gleichen import-Schlüsselwort importiert, aber ein Paket bietet eine Namespace-Hierarchie: greetings.english und greetings.spanish sind separate Module, teilen sich jedoch den greetings-Namespace.

Wann ein Modul, wann ein Paket verwenden:

SituationVerwende
Ein kleines, in sich geschlossenes HilfsprogrammModul (eine .py-Datei)
Mehrere verwandte Module unter einem gemeinsamen NamenPaket (ein Verzeichnis)
Eine Bibliothek, die auf PyPI veröffentlicht werden sollPaket (mit src/-Layout)

Die Datei __init__.py

__init__.py ist das, was ein Verzeichnis zu einem Paket macht. Python führt sie beim ersten Import des Pakets (oder eines seiner Module) aus. Sie kann leer sein oder:

  • Namen aus Untermodulen importieren, um sie auf Paketebene verfügbar zu machen
  • Paketweite Initialisierung durchführen (Logging-Konfiguration, Versions-Checks usw.)
  • __all__ definieren, um from package import * zu steuern

Minimales Paket-Layout

myapp/
    __init__.py       ← can be empty
    utils.py
    config.py
# myapp/__init__.py  (empty — that is fine)
# myapp/utils.py
def greet(name):
    return f"Hello, {name}!"

Import von außerhalb des Pakets:

from myapp.utils import greet

print(greet("Alice"))   # Hello, Alice!

Namen auf Paketebene bereitstellen

Ein gängiges Muster besteht darin, die am häufigsten verwendeten Namen in __init__.py zu importieren, damit Aufrufer from myapp import greet schreiben können statt from myapp.utils import greet.

# myapp/__init__.py
from .utils import greet
from .config import MAX_RETRIES

Nun sind beide Namen direkt am Paket verfügbar:

import myapp

print(myapp.greet("Bob"))   # Hello, Bob!
print(myapp.MAX_RETRIES)    # whatever config.py defines

Absolute Imports

Ein absoluter Import beginnt immer beim Top-Level-Paket oder einem Verzeichnis in sys.path. Er hängt nie davon ab, wo sich die importierende Datei befindet.

project/
    myapp/
        __init__.py
        utils.py
        services/
            __init__.py
            email.py

In email.py sieht ein absoluter Import so aus:

# myapp/services/email.py
from myapp.utils import greet   # absolute — starts from the top-level package

def send_welcome(user):
    message = greet(user)
    print(f"Sending: {message}")

Absolute Imports sind der Standard und der empfohlene Stil (PEP 8). Sie sind eindeutig, unabhängig davon, wie Python aufgerufen wird.

Relative Imports

Ein relativer Import verwendet Punkte (.), um relativ zum Speicherort der aktuellen Datei im Paketbaum zu navigieren.

  • . bezeichnet das aktuelle Paket
  • .. bezeichnet das übergeordnete Paket
  • ... bezeichnet das überübergeordnete Paket usw.
# myapp/services/email.py

# One dot — import from myapp.services (same directory)
from . import sms

# Two dots — import from myapp (parent directory)
from ..utils import greet

Wann relative Imports verwenden

Relative Imports sind innerhalb eines Pakets nützlich, wenn man deutlich machen möchte, dass greet aus diesem Paket stammt und nicht aus einer externen Bibliothek gleichen Namens. Sie erleichtern auch das Refactoring, da die Imports mit dem Paket mitziehen, wenn man das Top-Level-Verzeichnis umbenennt.

Achtung: Relative Imports funktionieren nur innerhalb eines Pakets. Wenn man python myapp/utils.py direkt ausführt, behandelt Python die Datei als eigenständiges Skript und nicht als Teil eines Pakets, und ein relativer Import löst ImportError: attempted relative import with no known parent package aus. Verwende stattdessen python -m myapp.utils.

# Wrong — runs utils.py as a script, breaking relative imports
$ python myapp/utils.py

# Right — runs utils.py as part of the myapp package
$ python -m myapp.utils

Die öffentliche API mit __all__ steuern

__all__ ist eine Liste von Namen, die from package import * exportiert. Sie dokumentiert auch, was das Paket als öffentlich betrachtet.

# myapp/__init__.py
from .utils import greet, farewell
from .config import MAX_RETRIES

__all__ = ["greet", "MAX_RETRIES"]   # farewell is intentionally not exported

Nun importiert from myapp import * nur greet und MAX_RETRIES. Die farewell-Funktion existiert weiterhin, ist aber nicht Teil der beworbenen öffentlichen Schnittstelle. Namen mit einem führenden Unterstrich (_private) werden per Konvention ebenfalls von import * ausgeschlossen.

Verschachtelte Pakete (Unterpakete)

Pakete können andere Pakete enthalten. Jedes Unterverzeichnis benötigt eine eigene __init__.py.

analytics/
    __init__.py
    reports/
        __init__.py
        daily.py
        weekly.py
    charts/
        __init__.py
        bar.py
        pie.py

Importiere ein tief verschachteltes Modul mit dem vollständigen gepunkteten Pfad:

from analytics.reports.daily import generate_report
from analytics.charts.bar import BarChart

Oder, wenn analytics/__init__.py sie bereitstellt:

# analytics/__init__.py
from .reports.daily import generate_report
# caller
from analytics import generate_report

Wie tief sollte man gehen?

Ein drei oder vier Ebenen tiefes Paket ist meist ein Zeichen dafür, dass es zu groß geworden ist und in separate Top-Level-Pakete aufgeteilt werden sollte (die unabhängig installiert werden können). Für die meisten Projekte sind zwei Ebenen (package.module) ausreichend.

Ein vollständiges Beispiel: Ein geometry-Paket aufbauen

Wir bauen Schritt für Schritt ein kleines, aber realistisches Paket.

Verzeichnis-Layout

geometry/
    __init__.py
    shapes.py
    conversions.py

shapes.py

# geometry/shapes.py
import math

def circle_area(radius):
    """Return the area of a circle with the given radius."""
    if radius < 0:
        raise ValueError("radius must be non-negative")
    return math.pi * radius ** 2

def rectangle_area(width, height):
    """Return the area of a rectangle."""
    return width * height

def triangle_area(base, height):
    """Return the area of a triangle."""
    return 0.5 * base * height

conversions.py

# geometry/conversions.py

def degrees_to_radians(degrees):
    """Convert degrees to radians."""
    import math
    return degrees * math.pi / 180

def radians_to_degrees(radians):
    """Convert radians to degrees."""
    import math
    return radians * 180 / math.pi

__init__.py — die wichtigsten Namen freilegen

# geometry/__init__.py
"""
geometry — simple 2-D geometry utilities.

Public API:
    circle_area(radius) -> float
    rectangle_area(width, height) -> float
    triangle_area(base, height) -> float
    degrees_to_radians(degrees) -> float
    radians_to_degrees(radians) -> float
"""

from .shapes import circle_area, rectangle_area, triangle_area
from .conversions import degrees_to_radians, radians_to_degrees

__all__ = [
    "circle_area",
    "rectangle_area",
    "triangle_area",
    "degrees_to_radians",
    "radians_to_degrees",
]

Das Paket verwenden

# main.py (sits next to the geometry/ directory)
import geometry

print(geometry.circle_area(5))           # 78.53981633974483
print(geometry.rectangle_area(4, 6))     # 24
print(geometry.degrees_to_radians(90))   # 1.5707963267948966

Oder mit selektiven Imports:

from geometry import circle_area, degrees_to_radians

print(circle_area(3))              # 28.274333882308138
print(degrees_to_radians(180))     # 3.141592653589793

Namespace-Pakete (Python 3.3+)

Seit Python 3.3 ist ein Verzeichnis ohne __init__.py ein Namespace-Paket. Python führt alle Verzeichnisse gleichen Namens in sys.path zu einem logischen Paket zusammen. Dies ist hauptsächlich für große Organisationen nützlich, die ein einzelnes Paket auf mehrere Repositories oder Installationsverzeichnisse aufteilen.

Für die tägliche Entwicklung empfiehlt es sich, immer __init__.py einzufügen. Das macht die Absicht eindeutig und funktioniert mit allen Python-Versionen.

Wie Python Pakete findet

Wenn man import geometry schreibt, durchsucht Python sys.path der Reihe nach:

  1. Das Verzeichnis des laufenden Skripts (oder das aktuelle Verzeichnis im interaktiven Modus)
  2. Verzeichnisse in der Umgebungsvariable PYTHONPATH
  3. Die Standardbibliotheks-Verzeichnisse
  4. Das site-packages-Verzeichnis (wo mit pip installierte Pakete liegen)
import sys
print(sys.path)

Das Paketverzeichnis muss sich direkt in einem dieser Orte befinden. Wenn geometry/ in /home/alice/projects/ liegt, findet Python es nicht, sofern /home/alice/projects/ nicht in sys.path eingetragen ist.

Tipp: Verwende eine virtuelle Umgebung und installiere dein Paket im Entwicklungsmodus (pip install -e .), damit Python es immer findet, ohne dass eine manuelle sys.path-Manipulation nötig ist.

Ein Paket verteilen

Um ein Paket mit anderen zu teilen (oder es mit pip zu installieren), benötigst du eine pyproject.toml im Projektstamm:

my_project/
    pyproject.toml    ← build metadata
    src/
        geometry/
            __init__.py
            shapes.py
            conversions.py

Eine minimale pyproject.toml:

[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.backends.legacy:build"

[project]
name = "geometry"
version = "0.1.0"
description = "Simple 2-D geometry utilities"
requires-python = ">=3.9"

Lokal im editierbaren Modus während der Entwicklung installieren:

pip install -e .

Nun funktioniert import geometry überall in der virtuellen Umgebung, unabhängig vom aktuellen Verzeichnis.

Häufige Fallstricke

Fehlende __init__.py

Wenn man __init__.py vergisst, behandelt Python 3 das Verzeichnis als Namespace-Paket (was meistens noch funktioniert), aber Python 2 ignoriert es vollständig. Sei explizit: füge immer __init__.py hinzu.

Ein Paket genauso benennen wie ein stdlib-Modul

Vermeide Namen wie math/, json/, os/, email/. Python könnte dein Paket anstelle des Standard-Bibliotheks-Moduls importieren und so unzusammenhängenden Code beschädigen.

Ein Paket-Modul als Skript ausführen

Wie oben beschrieben, bricht das direkte Ausführen von python myapp/services/email.py relative Imports. Verwende stattdessen python -m myapp.services.email.

Zirkuläre Imports zwischen Modulen desselben Pakets

Wenn shapes.py aus conversions.py importiert und conversions.py aus shapes.py importiert, liegt ein zirkulärer Import vor. Symptome sind ImportError oder Namen, die unerwartet als None erscheinen. Die Lösung besteht meistens darin, die gemeinsame Logik in ein drittes Modul auszulagern oder den Import in einen Funktionsrumpf zu verlagern.

# Delayed import — breaks the cycle at module load time
def some_function():
    from .shapes import circle_area   # imported only when the function is called
    ...

ImportError bei relativen Imports außerhalb eines Pakets

# Will raise: ImportError: attempted relative import with no known parent package
# if run as:  python myapp/utils.py

from . import config   # relative import inside utils.py

Führe es als python -m myapp.utils aus oder strukturiere es so um, dass der Einstiegspunkt ein separates Skript ist, das das Paket importiert.

Zusammenfassung

KonzeptKurzbeschreibung
PaketEin Verzeichnis mit __init__.py, das Module enthält
__init__.pyMacht ein Verzeichnis zu einem Paket; wird beim ersten Import ausgeführt
Absoluter Importfrom myapp.utils import greet — immer von der Wurzel
Relativer Importfrom ..utils import greet — relativ zur aktuellen Datei
__all__Listet Namen auf, die from package import * exportiert
Namespace-PaketVerzeichnis ohne __init__.py; nur Python 3.3+
Editierbares Installpip install -e . — Paket überall in der venv auffindbar

Siehe Python-Module für die Codeorganisation in einzelnen Dateien, Python pip zum Installieren von Drittanbieter-Paketen und Python Virtual Environments zum isolierten Verwalten von Projektabhängigkeiten.

Übungen

Übung
What makes a directory a Python package (in Python versions before 3.3)?
What makes a directory a Python package (in Python versions before 3.3)?
Was this page helpful?