Home

Anhang 2: Python Module, Packages und Imports verstehen

appendix python modules packages imports uv

Python Modules

1. Einführung: Das Import-System von Python entmystifizieren

Willkommen zu Anhang 2! Diese Vorlesung behandelt eine der häufigsten Verwirrungsquellen für Python-Entwickler: das Import-System. Wenn Sie jemals Fehler wie diese erlebt haben:

ImportError: attempted relative import with no known parent package
ModuleNotFoundError: No module named 'road_profile_viewer'
ImportError: cannot import name 'find_intersection' from 'geometry'

…dann ist diese Vorlesung genau richtig für Sie.

1.1 Warum das jetzt wichtig ist

Sie arbeiten gerade an der road-profile-viewer Refactoring-Aufgabe, bei der Sie ein monolithisches Python-Skript in ein modulares Projekt mit mehreren Dateien umwandeln:

road_profile_viewer/
├── geometry.py
├── road.py
├── visualization.py
└── main.py

Sobald Sie Code auf mehrere Dateien aufteilen, müssen Sie Funktionen aus einem Modul in ein anderes importieren. Und da entstehen die Fragen:

1.2 Was Sie lernen werden

Am Ende dieser Vorlesung werden Sie verstehen:

  1. Was Python-Module und Packages sind (und wie sie sich unterscheiden)
  2. Wann __init__.py benötigt wird (und wann es optional ist)
  3. Absolute Imports: Der empfohlene Ansatz für die meisten Projekte
  4. Relative Imports: Wie die Punkt-Notation funktioniert (und wann man sie verwendet)
  5. Wie externe Packages wie numpy anders funktionieren als Ihre eigenen Module
  6. Häufige Import-Fehler und wie man sie behebt
  7. Best Practices für die Strukturierung von Python-Projekten

1.3 Voraussetzungen

Diese Vorlesung baut auf Konzepten auf aus:

Sie sollten das initiale Setup des road-profile-viewer Projekts abgeschlossen haben.


2. Was ist ein Python-Modul?

Beginnen wir mit den Grundlagen: Was ist ein Modul?

2.1 Die einfache Definition

Für praktische Zwecke können Sie sich ein Python-Modul als eine .py-Datei vorstellen, die Python-Code enthält.

Das ist das einfachste mentale Modell. Wenn Sie geometry.py erstellen, haben Sie ein Modul namens geometry erstellt.

Offizielle Definition:

Laut dem Python Glossar ist ein Modul “ein Objekt, das als organisatorische Einheit von Python-Code dient”. Technisch gesehen können Module aus verschiedenen Quellen stammen (.py-Dateien, C-Erweiterungen, eingebaute Module), aber für diesen Kurs konzentrieren wir uns auf .py-Dateien—den häufigsten Typ, mit dem Sie arbeiten werden.

Für ein umfassendes Verständnis siehe:

2.2 Die Module Ihres Projekts

In Ihrem road-profile-viewer Projekt haben Sie vier Module:

road_profile_viewer/
├── geometry.py       ← Modul: geometry
├── road.py           ← Modul: road
├── visualization.py  ← Modul: visualization
└── main.py          ← Modul: main

Jede Datei ist eine eigenständige Einheit von Python-Code, die:

2.3 Was ist in einem Modul?

Schauen wir uns eine vereinfachte Version Ihres geometry.py an:

# geometry.py
import numpy as np
from typing import Optional, Tuple

def calculate_ray_line(
    x: float,
    ray_angle: float,
    line_start: Tuple[float, float],
    line_end: Tuple[float, float]
) -> Optional[Tuple[float, float]]:
    """Berechnet den Schnittpunkt zwischen einem Strahl und einem Liniensegment."""
    # Implementierung hier...
    return intersection_point

def find_intersection(x: float, ray_angle: float, road_profile: list) -> Optional[float]:
    """Findet den ersten Schnittpunkt eines Strahls mit dem Straßenprofil."""
    # Implementierung hier...
    return distance

Dieses Modul stellt zwei Funktionen bereit, die andere Teile Ihres Codes importieren und verwenden können.

2.4 Module vs. Skripte

Hier ist ein wichtiger Unterschied:

Eine Python-Datei kann auf zwei Arten verwendet werden:

  1. Als Modul (von anderem Code importiert):
    from geometry import calculate_ray_line
    
  2. Als Skript (direkt ausgeführt):
    python geometry.py
    

Die meisten Dateien in Ihrem Projekt sind Module, die importiert werden sollen, nicht direkt ausgeführt. Deshalb sehen Sie dieses Muster:

# main.py
if __name__ == "__main__":
    # Dies läuft nur, wenn als Skript ausgeführt
    main()

Dadurch kann main.py auf beide Arten funktionieren:

2.5 Wie Python Module findet

Wenn Sie import geometry schreiben, sucht Python nach geometry.py an diesen Orten in dieser Reihenfolge:

  1. Aktuelles Verzeichnis (wo Sie Python ausführen)
  2. Verzeichnisse in PYTHONPATH (Umgebungsvariable)
  3. site-packages/ der virtuellen Umgebung (installierte Packages)
  4. Standardbibliothek (eingebaute Module wie os, sys, json)

Sie können diese Liste sehen mit:

import sys
print(sys.path)

Ausgabe (Beispiel):

[
  '/home/student/road-profile-viewer',           # Aktuelles Verzeichnis
  '/home/student/.venv/lib/python3.12/site-packages',  # venv packages
  '/usr/lib/python3.12',                         # Standardbibliothek
]

Deshalb funktioniert import numpy: numpy ist im site-packages/-Verzeichnis Ihrer virtuellen Umgebung installiert.

2.6 Das Problem mit direkter Ausführung

Probieren Sie dieses Experiment:

# Dies wird FEHLSCHLAGEN mit Import-Fehlern:
cd road_profile_viewer
python geometry.py

Fehler:

ModuleNotFoundError: No module named 'road_profile_viewer'

Warum? Weil Python nicht weiß, dass road_profile_viewer ein Package ist, wenn Sie python geometry.py ausführen. Es sieht nur geometry.py als eigenständiges Skript.

Die Lösung (wir erklären später warum):

# Das FUNKTIONIERT:
cd ..  # Zum Projekt-Root gehen
python -m road_profile_viewer.geometry

Das -m Flag sagt Python, road_profile_viewer.geometry als Modul innerhalb eines Packages zu behandeln, nicht nur als Skript.

2.7 Wichtigste Erkenntnisse: Module


3. Was ist ein Python-Package?

Jetzt gehen wir eine Stufe höher: Was ist ein Package?

3.1 Die einfache Definition

Ein Python-Package ist ein Verzeichnis, das Python-Module enthält.

Mit anderen Worten: Ein Package ist ein Ordner mit .py-Dateien, den Python als zusammenhängende Einheit behandelt.

3.2 Ihr Projekt ist ein Package

Ihr road_profile_viewer-Verzeichnis ist ein Python-Package:

road_profile_viewer/          ← Dies ist ein PACKAGE
├── __init__.py              ← Optional (mehr dazu gleich)
├── geometry.py              ← Modul im Package
├── road.py                  ← Modul im Package
├── visualization.py         ← Modul im Package
└── main.py                  ← Modul im Package

Dieses Package enthält vier Module: geometry, road, visualization und main.

3.3 Packages ermöglichen hierarchische Organisation

Packages ermöglichen es Ihnen, Code in logische Gruppen zu organisieren:

my_project/
├── data/
│   ├── __init__.py
│   ├── loader.py
│   └── processor.py
├── models/
│   ├── __init__.py
│   ├── neural_net.py
│   └── decision_tree.py
└── utils/
    ├── __init__.py
    ├── math.py
    └── plotting.py

Hier haben wir drei Packages (data, models, utils), jedes mit zugehörigen Modulen.

3.4 Die Rolle von __init__.py

Dies ist eine der häufigsten Verwirrungsquellen!

3.4.1 Python 3.3+ (Modernes Python)

In Python 3.3 und später ist __init__.py optional für einfache Packages. Python unterstützt “Namespace Packages”, die kein __init__.py benötigen.

Ihr Projekt funktioniert ohne __init__.py:

road_profile_viewer/
├── geometry.py
├── road.py
├── visualization.py
└── main.py
# Kein __init__.py nötig!

Sie können trotzdem Module importieren:

from road_profile_viewer.geometry import find_intersection  # ✅ Funktioniert!

3.4.2 Wann __init__.py immer noch nützlich ist

Obwohl es optional ist, erfüllt __init__.py wichtige Zwecke:

1. Package-Initialisierungscode

Sie können Setup-Code ausführen, wenn das Package zum ersten Mal importiert wird:

# road_profile_viewer/__init__.py
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

logger.info("Road Profile Viewer Package geladen")

⚠️ Wichtige Warnung: Minimieren Sie Code in __init__.py

Obwohl es verlockend ist, hier Initialisierungscode hinzuzufügen, halten Sie __init__.py so minimal wie möglich:

Warum? Code in __init__.py wird jedes Mal ausgeführt, wenn das Package importiert wird, was:

Best Practice: Fügen Sie nur Code zu __init__.py hinzu, wenn es absolut notwendig ist, damit das Package funktioniert. Und selbst dann brauchen Sie es wahrscheinlich nicht so sehr, wie Sie denken! 😉

Besserer Ansatz für Logging:

# Anstatt Logging in __init__.py zu konfigurieren, tun Sie es in Ihrem Haupteinstiegspunkt:
# main.py
import logging

def main():
    logging.basicConfig(level=logging.INFO)
    logger = logging.getLogger(__name__)
    # Ihr Anwendungscode...

2. Bequeme Imports

Häufig verwendete Funktionen auf Package-Ebene verfügbar machen:

# road_profile_viewer/__init__.py
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile
from road_profile_viewer.visualization import create_dash_app

# Jetzt können Benutzer dies tun:
from road_profile_viewer import find_intersection  # ✅ Kürzer!

# Anstatt:
from road_profile_viewer.geometry import find_intersection  # ✅ Funktioniert auch

3. __all__ definieren

Kontrollieren, was from package import * importiert:

# road_profile_viewer/__init__.py
__all__ = ['find_intersection', 'generate_road_profile', 'create_dash_app']

⚠️ Wichtige Hinweise zu __all__ und import *

Während __all__ für Bibliotheksautoren nützlich ist, um eine öffentliche API zu definieren, gibt es wichtige Vorbehalte:

Über import * (Stern-Imports):

Warum __all__ trotzdem wichtig ist:

Best Practice:

# ❌ MACHEN SIE DAS NICHT:
from road_profile_viewer import *  # Was wurde importiert? Wer weiß!

# ✅ MACHEN SIE STATTDESSEN:
from road_profile_viewer import find_intersection, generate_road_profile  # Explizit und klar

Performance-Hinweis: In großen Projekten kann selbst die Definition von __all__ Auswirkungen haben. Wenn Ihr __init__.py viele Module importiert, nur um __all__ zu füllen, laden Sie Code, der möglicherweise nicht benötigt wird. Deshalb verwenden viele große Bibliotheken lazy imports oder halten __all__ minimal.

4. Package-Metadaten

Versionsinformationen und Package-Konstanten speichern:

# road_profile_viewer/__init__.py
__version__ = "1.0.0"
__author__ = "Ihr Name"

# Verwendung woanders:
import road_profile_viewer
print(road_profile_viewer.__version__)

3.4.3 Reguläre Packages vs. Namespace Packages

Reguläres Package (hat __init__.py):

my_package/
├── __init__.py    ← Macht dies zu einem regulären Package
└── module.py

Namespace Package (kein __init__.py):

my_package/
└── module.py      ← Funktioniert trotzdem in Python 3.3+

Wann welches verwenden:

3.5 Hands-On: __init__.py zu Ihrem Projekt hinzufügen

Fügen wir ein minimales __init__.py zu Ihrem road-profile-viewer hinzu:

Erstellen Sie road_profile_viewer/__init__.py:

"""
Road Profile Viewer

Ein Python-Package zur Visualisierung von Straßenhöhenprofilen mit interaktivem Ray-Tracing.
"""

__version__ = "1.0.0"

# Bequeme Imports für häufige Anwendungsfälle
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile
from road_profile_viewer.visualization import create_dash_app

__all__ = [
    'find_intersection',
    'generate_road_profile',
    'create_dash_app',
]

Vorteile:

# Vorher (Namespace Package):
from road_profile_viewer.geometry import find_intersection

# Nachher (Reguläres Package mit __init__.py):
from road_profile_viewer import find_intersection  # ✅ Kürzer!

# Versionsinformationen:
import road_profile_viewer
print(road_profile_viewer.__version__)  # ✅ "1.0.0"

3.6 Verschachtelte Packages

Packages können andere Packages enthalten:

road_profile_viewer/
├── __init__.py
├── main.py
├── core/
│   ├── __init__.py
│   ├── geometry.py
│   └── road.py
└── ui/
    ├── __init__.py
    └── visualization.py

Jetzt haben Sie:

Import-Pfade spiegeln diese Struktur wider:

from road_profile_viewer.core.geometry import find_intersection
from road_profile_viewer.ui.visualization import create_dash_app

3.7 Wichtigste Erkenntnisse: Packages


Jetzt gehen wir den wichtigsten Import-Stil an: absolute Imports.

4.1 Was sind absolute Imports?

Absolute Imports verwenden den vollständigen Pfad vom Projekt-Root.

Syntax:

from package.module import function

4.2 Absolute Imports in Ihrem Projekt

In Ihrem road-profile-viewer sehen absolute Imports so aus:

In visualization.py:

# Import aus dem geometry-Modul
from road_profile_viewer.geometry import find_intersection

# Import aus dem road-Modul
from road_profile_viewer.road import generate_road_profile

In main.py:

# Import aus dem visualization-Modul
from road_profile_viewer.visualization import create_dash_app

4.3 Wie absolute Imports funktionieren

Wenn Python from road_profile_viewer.geometry import find_intersection sieht, macht es:

  1. Sucht nach road_profile_viewer in sys.path
  2. Sucht nach geometry.py in diesem Package
  3. Importiert die Funktion find_intersection aus diesem Modul

Visuelle Darstellung:

sys.path enthält: /home/student/project/
                   ↓
                   road_profile_viewer/    ← Gefunden!
                   ↓
                   geometry.py             ← Gefunden!
                   ↓
                   def find_intersection   ← Importiere dies!

4.4 Warum die Aufgabe absolute Imports erfordert

Ihre GitHub Classroom-Aufgabe besagt:

Absolute Imports sind verpflichtend, um “ImportError: attempted relative import with no known parent package” zu vermeiden.

Warum diese Anforderung? Schauen wir uns an, was mit verschiedenen Ausführungsbefehlen passiert:

Szenario 1: Als Skript ausführen (SCHEITERT mit relativen Imports)

cd road_profile_viewer
python main.py

Wenn main.py relative Imports wie from .visualization import create_dash_app verwendet, erhalten Sie:

ImportError: attempted relative import with no known parent package

Warum? Python weiß nicht, dass road_profile_viewer ein Package ist, wenn Sie python main.py direkt ausführen.

Szenario 2: Als Modul ausführen (FUNKTIONIERT)

cd ..  # Projekt-Root
python -m road_profile_viewer.main

Jetzt weiß Python:

Szenario 3: Absolute Imports verwenden (FUNKTIONIERT IMMER)

# Von überall:
python -m road_profile_viewer.main  # ✅ Funktioniert
cd road_profile_viewer && python main.py  # ✅ Funktioniert auch!

Absolute Imports funktionieren, weil sie nicht davon abhängen, dass Python die Package-Struktur kennt - sie verwenden sys.path.

4.5 Module ausführen: Das -m Flag

Best Practice: Verwenden Sie immer -m zum Ausführen von Modulen in Packages:

# ✅ GUT: Als Modul ausführen
python -m road_profile_viewer.main

# ❌ SCHLECHT: Als Skript ausführen (bricht relative Imports)
python road_profile_viewer/main.py

Das -m Flag sagt Python:

4.6 Wie uv run damit umgeht

Wenn Sie uv run verwenden:

uv run python -m road_profile_viewer.main

uv macht automatisch:

4.7 Vollständiges Beispiel: Absolute Imports in Ihrem Projekt

Datei: road_profile_viewer/geometry.py

"""Geometrieberechnungen für Strahl-Linien-Schnittpunkte."""
import numpy as np
from typing import Optional, Tuple

def find_intersection(x: float, ray_angle: float, road_profile: list) -> Optional[float]:
    """Findet Schnittpunkt zwischen Strahl und Straßenprofil."""
    # Implementierung...
    return distance

Datei: road_profile_viewer/road.py

"""Straßenprofil-Generierung."""
import numpy as np

def generate_road_profile(num_points: int = 100) -> np.ndarray:
    """Generiert ein zufälliges Straßenhöhenprofil."""
    # Implementierung...
    return profile

Datei: road_profile_viewer/visualization.py

"""Dash-Webanwendung zur Visualisierung."""
import dash
from dash import dcc, html
import plotly.graph_objects as go

# ✅ Absolute Imports
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile

def create_dash_app():
    """Erstellt und gibt eine Dash-Anwendung zurück."""
    app = dash.Dash(__name__)

    # Verwendung importierter Funktionen
    profile = generate_road_profile()
    distance = find_intersection(0, 45, profile)

    # UI aufbauen...
    return app

Datei: road_profile_viewer/main.py

"""Einstiegspunkt für die Road Profile Viewer-Anwendung."""

# ✅ Absoluter Import
from road_profile_viewer.visualization import create_dash_app

def main():
    """Führt die Anwendung aus."""
    app = create_dash_app()
    app.run_server(debug=True, host='127.0.0.1', port=8050)

if __name__ == "__main__":
    main()

Die App ausführen:

# Vom Projekt-Root:
uv run python -m road_profile_viewer.main

4.8 Vorteile absoluter Imports

Klarheit: Sofort offensichtlich, woher Code kommt ✅ Zuverlässigkeit: Funktioniert unabhängig davon, wie das Modul ausgeführt wird ✅ IDE-Unterstützung: Bessere Autovervollständigung und Navigation ✅ Refactoring-Sicherheit: Dateien verschieben bricht keine Imports (solange Package-Struktur gleich bleibt) ✅ Keine Mehrdeutigkeit: Kann nicht mit Standardbibliothek oder externen Packages verwechselt werden

4.9 Wichtigste Erkenntnisse: Absolute Imports


5. Relative Imports: Wann und wie

Jetzt erkunden wir relative Imports - den alternativen Ansatz mit Punkten.

5.1 Was sind relative Imports?

Relative Imports verwenden Punkte zur Navigation in der Package-Hierarchie.

Syntax:

from .module import function       # Aktuelles Package
from ..module import function      # Eltern-Package
from ...module import function     # Großeltern-Package

5.2 Die Punkt-Notation erklärt

Denken Sie an Punkte wie Dateisystempfade:

5.3 Ihr Projekt mit relativen Imports

Datei: road_profile_viewer/visualization.py

# Relative Imports (Alternative zu absoluten)
from .geometry import find_intersection      # ← Ein Punkt
from .road import generate_road_profile      # ← Ein Punkt

Übersetzung:

Datei: road_profile_viewer/main.py

# Relativer Import
from .visualization import create_dash_app   # ← Ein Punkt

5.4 Die Punkte verstehen: Visueller Leitfaden

Stellen Sie sich diese komplexere Struktur vor:

my_project/
├── package_a/
│   ├── __init__.py
│   ├── module1.py
│   └── sub_package/
│       ├── __init__.py
│       └── module2.py
└── package_b/
    ├── __init__.py
    └── module3.py

Von package_a/sub_package/module2.py:

# Import aus dem gleichen Package (sub_package)
from . import module2           # ← Aktuelles Package (sub_package)

# Import aus dem Eltern-Package (package_a)
from .. import module1          # ← Eine Ebene nach oben

# Import aus Geschwistermodul im Eltern-Package
from ..module1 import function  # ← Hoch dann in module1

# Import aus völlig anderem Package (FUNKTIONIERT NICHT)
from ...package_b import module3  # ❌ Kann nicht über Root hinaus!

Visuelle Darstellung:

module2.py Ort: package_a/sub_package/module2.py

from .       → package_a/sub_package/  (aktuell)
from ..      → package_a/               (eine nach oben)
from ...     → my_project/              (zwei nach oben - root)
from ....    → ❌ FEHLER: jenseits von root

5.5 Relativ vs. Absolut: Nebeneinander

Vergleichen wir die gleichen Imports in beiden Varianten:

Absolute Imports:

# visualization.py
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile

Relative Imports:

# visualization.py
from .geometry import find_intersection
from .road import generate_road_profile

Beide funktionieren gleich! Die Wahl ist eine Frage des Stils und der Projektanforderungen.

5.6 Wann relative Imports brechen: Der berüchtigte Fehler

Versuchen Sie, Ihren Code mit relativen Imports auszuführen:

cd road_profile_viewer
python main.py

Fehler:

ImportError: attempted relative import with no known parent package

Warum passiert das?

Wenn Sie python main.py ausführen, behandelt Python main.py als eigenständiges Skript, nicht als Modul in einem Package. Relative Imports erfordern, dass Python weiß:

Bei direkter Ausführung als Skript hat Python diese Informationen nicht!

Die Lösung:

# Als Modul ausführen, nicht als Skript:
cd ..  # Zum Projekt-Root gehen
python -m road_profile_viewer.main  # ✅ Funktioniert!

Jetzt weiß Python:

5.7 Wann relative Imports verwenden

Relative Imports sind nützlich, wenn:

Große Packages mit tiefen Hierarchien ✅ Package wird umbenannt (relative Imports kodieren den Package-Namen nicht fest) ✅ Sub-Packages, die eigenständig sein sollen

Beispiel: Eine Bibliothek, die geforkt werden könnte:

# Wenn Ihr Package von "original_name" zu "forked_name" umbenannt werden könnte
# Relative Imports brechen nicht:
from .utils import helper  # ✅ Funktioniert mit jedem Package-Namen

# Absolute Imports würden Updates benötigen:
from original_name.utils import helper  # ❌ Bricht nach Umbenennung

5.8 Wann absolute Imports verwenden

Absolute Imports sind besser, wenn:

Kleine bis mittelgroße Projekte (wie road-profile-viewer) ✅ Module als Skripte ausführen (keine Eltern-Package-Verwirrung) ✅ Team-Projekte (klarer, expliziter) ✅ Kurs-Aufgaben (für diesen Kurs erforderlich!)

5.9 Absolute und Relative mischen

Sie können beide Stile im selben Projekt mischen:

# visualization.py
from road_profile_viewer.geometry import find_intersection  # Absolut
from .road import generate_road_profile                      # Relativ
import numpy as np                                           # Externes Package

Konsistenz ist jedoch besser! Wählen Sie einen Stil und bleiben Sie dabei im gesamten Projekt.

5.10 Wichtigste Erkenntnisse: Relative Imports


6. Absolut vs. Relativ: Der praktische Vergleich

Machen wir einen umfassenden Vergleich, um Ihnen zu helfen, informierte Entscheidungen zu treffen.

6.1 Szenario: Refactoring in Unterverzeichnisse

Stellen Sie sich vor, Sie reorganisieren Ihr Projekt in Unterverzeichnisse:

Aktuelle Struktur:

road_profile_viewer/
├── geometry.py
├── road.py
├── visualization.py
└── main.py

Neue Struktur:

road_profile_viewer/
├── core/
│   ├── geometry.py
│   └── road.py
├── ui/
│   └── visualization.py
└── main.py

6.2 Mit absoluten Imports

Vorher (flache Struktur):

# visualization.py
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile

Nachher (verschachtelte Struktur) - ERFORDERT ÄNDERUNGEN:

# ui/visualization.py
from road_profile_viewer.core.geometry import find_intersection
from road_profile_viewer.core.road import generate_road_profile

Auswirkung: ❌ Muss Import-Pfade in allen Dateien aktualisieren

6.3 Mit relativen Imports

Vorher (flache Struktur):

# visualization.py
from .geometry import find_intersection
from .road import generate_road_profile

Nachher (verschachtelte Struktur) - ERFORDERT AUCH ÄNDERUNGEN:

# ui/visualization.py
from ..core.geometry import find_intersection
from ..core.road import generate_road_profile

Auswirkung: ❌ Muss Import-Pfade aktualisieren (aber nur Punkte ändern sich, nicht der vollständige Pfad)

6.4 Vergleichstabelle

Aspekt Absolute Imports Relative Imports
Klarheit ✅ Sofort klar, woher Code kommt ⚠️ Muss Punkte mental navigieren
Zuverlässigkeit ✅ Funktioniert als Skript oder Modul ❌ Nur mit python -m
IDE-Unterstützung ✅ Bessere Autovervollständigung und Navigation ⚠️ Kann bei komplexer Verschachtelung Probleme haben
Refactoring ❌ Muss vollständige Pfade bei Umstrukturierung aktualisieren ⚠️ Muss Punkte beim Ändern von Ebenen aktualisieren
Package-Umbenennung ❌ Muss alle Imports aktualisieren, wenn Package umbenannt wird ✅ Keine Änderungen nötig
Lernkurve ✅ Einfacher für Anfänger ⚠️ Erfordert Verständnis der Package-Hierarchie
Ausführlichkeit ⚠️ Längere Import-Anweisungen ✅ Kürzer, prägnanter

6.5 Echter Code-Vergleich

Schauen wir uns ein vollständiges Beispiel nebeneinander an:

Dateistruktur:

road_profile_viewer/
├── core/
│   ├── __init__.py
│   ├── geometry.py
│   └── road.py
├── ui/
│   ├── __init__.py
│   └── visualization.py
└── main.py

Version mit absoluten Imports:

# ui/visualization.py
from road_profile_viewer.core.geometry import find_intersection
from road_profile_viewer.core.road import generate_road_profile
import dash

def create_dash_app():
    profile = generate_road_profile()
    return dash.Dash(__name__)
# main.py
from road_profile_viewer.ui.visualization import create_dash_app

def main():
    app = create_dash_app()
    app.run_server()

if __name__ == "__main__":
    main()

Version mit relativen Imports:

# ui/visualization.py
from ..core.geometry import find_intersection
from ..core.road import generate_road_profile
import dash

def create_dash_app():
    profile = generate_road_profile()
    return dash.Dash(__name__)
# main.py
from .ui.visualization import create_dash_app

def main():
    app = create_dash_app()
    app.run_server()

if __name__ == "__main__":
    main()

Ausführung:

# Absolute Imports: Funktioniert auf beide Arten
python road_profile_viewer/main.py              # ✅ Funktioniert
python -m road_profile_viewer.main              # ✅ Funktioniert

# Relative Imports: Funktioniert nur auf eine Art
python road_profile_viewer/main.py              # ❌ ImportError!
python -m road_profile_viewer.main              # ✅ Funktioniert

6.6 Empfehlung für diesen Kurs

Verwenden Sie absolute Imports für die road-profile-viewer Aufgabe, weil:

  1. ✅ Von der Aufgabenspezifikation gefordert
  2. ✅ Zuverlässiger für Anfänger
  3. ✅ Bessere IDE-Unterstützung in VS Code
  4. ✅ Einfacher für Peer-Code-Review
  5. ✅ Konsistent mit Kursbeispielen

Format:

from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile
from road_profile_viewer.visualization import create_dash_app

6.7 Wichtigste Erkenntnisse: Vergleich


7. Externe Packages: Wie import numpy einfach funktioniert

Jetzt beantworten wir die brennende Frage: Warum kann ich import numpy as np schreiben, aber das funktioniert nicht mit meinen eigenen Modulen?

7.1 Das Geheimnis einfacher Imports

Sie haben dieses Muster bemerkt:

# Externe Packages: Einfache Imports ✅
import numpy as np
import pandas as pd
import dash

# Ihr eigener Code: Vollständiger Pfad erforderlich ❌
import geometry  # ModuleNotFoundError!

# Ihr eigener Code: Muss vollständigen Pfad verwenden ✅
from road_profile_viewer.geometry import find_intersection

Warum der Unterschied?

7.2 Wo externe Packages leben

Wenn Sie uv add numpy ausführen, installiert uv numpy im site-packages/-Verzeichnis Ihrer virtuellen Umgebung:

.venv/
└── lib/
    └── python3.12/
        └── site-packages/
            ├── numpy/              ← numpy Package
            │   ├── __init__.py
            │   ├── core/
            │   ├── linalg/
            │   └── ...
            ├── pandas/             ← pandas Package
            ├── dash/               ← dash Package
            └── plotly/             ← plotly Package

7.3 Wie Python externe Packages findet

Erinnern Sie sich an sys.path? Es enthält site-packages/:

import sys
print(sys.path)

Ausgabe:

[
  '/home/student/road-profile-viewer',                        # Aktuelles Verzeichnis
  '/home/student/.venv/lib/python3.12/site-packages',         # ← Installierte Packages!
  '/usr/lib/python3.12',                                       # Standardbibliothek
]

Wenn Sie import numpy schreiben, macht Python:

  1. Durchsucht sys.path in Reihenfolge
  2. Findet numpy/-Verzeichnis in site-packages/
  3. Importiert numpy/__init__.py
  4. Erfolg!

7.4 Warum Ihre eigenen Module nicht so funktionieren

Ihre Projektstruktur:

road-profile-viewer/              ← Projekt-Root (in sys.path)
└── road_profile_viewer/          ← Package-Verzeichnis
    ├── geometry.py               ← Modul
    ├── road.py                   ← Modul
    └── main.py                   ← Modul

Wenn Sie import geometry schreiben, macht Python:

  1. Durchsucht sys.path
  2. Sucht nach geometry.py oder geometry/-Verzeichnis im Projekt-Root
  3. Findet es nicht (weil es in road_profile_viewer/ ist)
  4. ModuleNotFoundError!

Ihre Module sind in einem Package verschachtelt, also müssen Sie den vollständigen Pfad verwenden:

from road_profile_viewer.geometry import find_intersection  # ✅ Funktioniert

7.5 Pythons Import-Suchreihenfolge

Wenn Sie etwas importieren, sucht Python in dieser Reihenfolge:

  1. Eingebaute Module (wie sys, os, json)
  2. Aktuelles Verzeichnis (wo Sie Python ausführen)
  3. Verzeichnisse in PYTHONPATH (Umgebungsvariable)
  4. site-packages/ der virtuellen Umgebung (installierte Packages)
  5. site-packages/ des System-Python (globale Packages - normalerweise in venv ignoriert)
  6. Standardbibliothek (Pythons eingebaute Module)

Beispiel-Auflösung:

import sys          # ← Gefunden in eingebauten Modulen (Schritt 1)
import numpy        # ← Gefunden in venv's site-packages (Schritt 4)
import geometry     # ← Nirgends gefunden → ModuleNotFoundError!

7.6 Ihr eigenes Package installieren (Fortgeschritten)

Was, wenn Sie Ihren eigenen Code wie import road_profile_viewer importieren möchten?

Sie können Ihr eigenes Projekt als Package in site-packages/ installieren:

# Installieren Sie Ihr Projekt im "editierbaren" Modus
uv pip install -e .

Dies erstellt einen Link in site-packages/, der auf Ihr Projekt zeigt:

.venv/lib/python3.12/site-packages/
├── numpy/
├── dash/
└── road_profile_viewer.egg-link  ← Zeigt auf Ihr Projekt!

Jetzt funktioniert dies:

# Von überall, nicht nur vom Projekt-Root:
import road_profile_viewer
from road_profile_viewer.geometry import find_intersection

Wann dies tun:

Wann NICHT tun (für diesen Kurs):

7.7 Die Gefahr: Namenskonflikte

⚠️ Benennen Sie Ihre Module NIEMALS wie Standardbibliothek oder beliebte Packages!

Schlechte Idee:

my_project/
├── numpy.py     ← ❌ Überschattet das echte numpy!
├── json.py      ← ❌ Überschattet eingebautes json!
└── main.py

Was passiert:

# main.py
import numpy  # ← Importiert IHR numpy.py, nicht das echte numpy Package!

Python durchsucht das aktuelle Verzeichnis zuerst, also wird Ihr numpy.py vor dem echten numpy in site-packages/ gefunden.

Ergebnis: Nichts funktioniert, und Fehlermeldungen sind verwirrend!

Wie vermeiden:

✅ Verwenden Sie beschreibende, eindeutige Namen für Ihre Module ✅ Verwenden Sie niemals Namen aus der Standardbibliothek (os, sys, json, math, etc.) ✅ Überprüfen Sie PyPI vor der Benennung eines Packages: pypi.org

7.8 Den Unterschied visualisieren

Externe Packages:

import numpy
       ↓
sys.path enthält .venv/lib/python3.12/site-packages/
       ↓
Gefunden: site-packages/numpy/
       ↓
Import numpy/__init__.py
       ↓
✅ Erfolg!

Ihre Module:

import geometry
       ↓
sys.path enthält /home/student/road-profile-viewer/
       ↓
NICHT gefunden: /home/student/road-profile-viewer/geometry.py
             (weil es im road_profile_viewer/ Unterverzeichnis ist)
       ↓
❌ ModuleNotFoundError!

RICHTIGER WEG:
from road_profile_viewer.geometry import ...
       ↓
sys.path enthält /home/student/road-profile-viewer/
       ↓
Gefunden: /home/student/road-profile-viewer/road_profile_viewer/
       ↓
Gefunden: geometry.py in road_profile_viewer/
       ↓
✅ Erfolg!

7.9 Wichtigste Erkenntnisse: Externe Packages


8. Hands-On: Das Import-System Ihres Projekts erkunden

Zeit, die Hände schmutzig zu machen! Lassen Sie uns erkunden, wie Imports in Ihrem tatsächlichen Projekt funktionieren.

8.1 Übung 1: sys.path inspizieren

Erstellen Sie eine neue Datei: road_profile_viewer/debug_imports.py

"""Debug-Skript zum Verständnis des Python-Import-Systems."""
import sys
import os

print("=" * 60)
print("PYTHON IMPORT SYSTEM DEBUG")
print("=" * 60)

print("\n1. AKTUELLES ARBEITSVERZEICHNIS:")
print(f"   {os.getcwd()}")

print("\n2. SKRIPT-ORT:")
print(f"   {__file__}")

print("\n3. sys.path (wo Python nach Modulen sucht):")
for i, path in enumerate(sys.path, 1):
    print(f"   [{i}] {path}")

print("\n4. VIRTUELLE UMGEBUNG:")
venv = os.environ.get('VIRTUAL_ENV', 'Nicht aktiviert')
print(f"   {venv}")

print("\n5. VERSUCHE UNSERE MODULE ZU IMPORTIEREN:")
try:
    from road_profile_viewer.geometry import find_intersection
    print("   ✅ Erfolgreich aus road_profile_viewer.geometry importiert")
except ImportError as e:
    print(f"   ❌ Import fehlgeschlagen: {e}")

print("\n6. VERSUCHE EXTERNE PACKAGES ZU IMPORTIEREN:")
try:
    import numpy
    print(f"   ✅ numpy Version {numpy.__version__} von {numpy.__file__}")
except ImportError as e:
    print(f"   ❌ numpy Import fehlgeschlagen: {e}")

print("=" * 60)

Ausführen:

# Stellen Sie sicher, dass Sie im Projekt-Root sind
cd /path/to/road-profile-viewer

# Als Modul ausführen
uv run python -m road_profile_viewer.debug_imports

Erwartete Ausgabe:

============================================================
PYTHON IMPORT SYSTEM DEBUG
============================================================

1. AKTUELLES ARBEITSVERZEICHNIS:
   /home/student/road-profile-viewer

2. SKRIPT-ORT:
   /home/student/road-profile-viewer/road_profile_viewer/debug_imports.py

3. sys.path (wo Python nach Modulen sucht):
   [1] /home/student/road-profile-viewer
   [2] /home/student/road-profile-viewer/.venv/lib/python3.12/site-packages
   [3] /usr/lib/python3.12
   [4] /usr/lib/python3.12/lib-dynload

4. VIRTUELLE UMGEBUNG:
   /home/student/road-profile-viewer/.venv

5. VERSUCHE UNSERE MODULE ZU IMPORTIEREN:
   ✅ Erfolgreich aus road_profile_viewer.geometry importiert

6. VERSUCHE EXTERNE PACKAGES ZU IMPORTIEREN:
   ✅ numpy Version 1.26.0 von /home/student/road-profile-viewer/.venv/lib/python3.12/site-packages/numpy/__init__.py
============================================================

Beobachtungen:

8.2 Übung 2: Bequeme Imports mit __init__.py hinzufügen

Erstellen/bearbeiten Sie: road_profile_viewer/__init__.py

"""
Road Profile Viewer

Ein Python-Package zur Visualisierung von Straßenhöhenprofilen.
"""

__version__ = "1.0.0"
__author__ = "Ihr Name"

# Bequeme Imports - häufig verwendete Funktionen auf Package-Ebene verfügbar machen
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile
from road_profile_viewer.visualization import create_dash_app

__all__ = [
    'find_intersection',
    'generate_road_profile',
    'create_dash_app',
]

# Optional: Protokollieren, wenn Package importiert wird
import logging
logger = logging.getLogger(__name__)
logger.info(f"Road Profile Viewer v{__version__} geladen")

Testen:

uv run python
>>> import road_profile_viewer
>>> print(road_profile_viewer.__version__)
1.0.0

>>> # Jetzt können wir von der Package-Ebene importieren!
>>> from road_profile_viewer import find_intersection
>>> print(find_intersection)
<function find_intersection at 0x...>

>>> # Anstatt des längeren:
>>> from road_profile_viewer.geometry import find_intersection

Vorteile:

8.3 Übung 3: Absolute vs. Relative Imports vergleichen

Erstellen Sie eine Test-Datei: road_profile_viewer/import_test.py

"""Teste verschiedene Import-Stile."""

print("Teste absolute Imports:")
try:
    from road_profile_viewer.geometry import find_intersection
    print("✅ Absoluter Import funktioniert!")
except ImportError as e:
    print(f"❌ Absoluter Import fehlgeschlagen: {e}")

print("\nTeste relative Imports:")
try:
    from .geometry import find_intersection
    print("✅ Relativer Import funktioniert!")
except ImportError as e:
    print(f"❌ Relativer Import fehlgeschlagen: {e}")

print("\nTeste einfachen Import (wird fehlschlagen):")
try:
    from geometry import find_intersection
    print("✅ Einfacher Import funktioniert!")
except ImportError as e:
    print(f"❌ Einfacher Import fehlgeschlagen: {e}")

Als Modul testen:

uv run python -m road_profile_viewer.import_test

Erwartete Ausgabe:

Teste absolute Imports:
✅ Absoluter Import funktioniert!

Teste relative Imports:
✅ Relativer Import funktioniert!

Teste einfachen Import (wird fehlschlagen):
❌ Einfacher Import fehlgeschlagen: No module named 'geometry'

Jetzt als Skript versuchen (wird fehlschlagen):

cd road_profile_viewer
uv run python import_test.py

Erwartete Ausgabe:

Teste absolute Imports:
✅ Absoluter Import funktioniert!

Teste relative Imports:
❌ Relativer Import fehlgeschlagen: attempted relative import with no known parent package

Teste einfachen Import (wird fehlschlagen):
❌ Einfacher Import fehlgeschlagen: No module named 'geometry'

Wichtige Lektion: Relative Imports funktionieren nur, wenn mit python -m ausgeführt!

8.4 Übung 4: Wo numpy lebt erkunden

uv run python
>>> import numpy
>>> print(numpy.__file__)
/home/student/road-profile-viewer/.venv/lib/python3.12/site-packages/numpy/__init__.py

>>> # Besser: Verwenden Sie __path__, um das Package-Verzeichnis direkt zu sehen
>>> print(numpy.__path__)
['/home/student/road-profile-viewer/.venv/lib/python3.12/site-packages/numpy']

>>> # Das gesamte Package-Verzeichnis sehen
>>> import os
>>> numpy_dir = os.path.dirname(numpy.__file__)
>>> print(f"numpy ist installiert unter: {numpy_dir}")
numpy ist installiert unter: /home/student/road-profile-viewer/.venv/lib/python3.12/site-packages/numpy

>>> # Einige von numpys Modulen auflisten
>>> os.listdir(numpy_dir)[:10]
['__init__.py', '__pycache__', 'core', 'distutils', 'doc', 'f2py', 'fft', 'lib', 'linalg', 'ma']

Beobachtungen:

8.5 Übung 5: Was aus Ihrem Package importierbar ist sehen

>>> import road_profile_viewer
>>> dir(road_profile_viewer)
['__all__', '__author__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', '__version__', 'create_dash_app', 'find_intersection', 'generate_road_profile', 'logging', 'logger']

>>> # Nur exportierte Namen (von __all__):
>>> road_profile_viewer.__all__
['find_intersection', 'generate_road_profile', 'create_dash_app']

Dies zeigt, was Ihr __init__.py verfügbar gemacht hat!

8.6 Wichtigste Erkenntnisse: Hands-On-Erkundung


9. Häufige Import-Fehler und Lösungen

Lassen Sie uns die häufigsten Import-Fehler angehen, denen Sie begegnen werden, und wie man sie behebt.

9.1 Fehler 1: ImportError: attempted relative import with no known parent package

Wann Sie es sehen:

$ python road_profile_viewer/main.py
ImportError: attempted relative import with no known parent package

Was ist passiert:

Sie verwenden relative Imports (wie from .visualization import), führen aber die Datei als Skript statt als Modul aus.

Warum es passiert:

Wenn Sie python road_profile_viewer/main.py ausführen, weiß Python nicht, dass main.py Teil des road_profile_viewer Packages ist. Relative Imports erfordern Package-Kontext.

Lösungen:

Option 1: python -m verwenden (empfohlen)

cd /path/to/project-root
python -m road_profile_viewer.main

Option 2: Stattdessen absolute Imports verwenden

# Ändern Sie dies:
from .visualization import create_dash_app

# Zu diesem:
from road_profile_viewer.visualization import create_dash_app

Option 3: uv run verwenden

uv run python -m road_profile_viewer.main

9.2 Fehler 2: ModuleNotFoundError: No module named 'road_profile_viewer'

Wann Sie es sehen:

$ python -m road_profile_viewer.main
ModuleNotFoundError: No module named 'road_profile_viewer'

Was ist passiert:

Python kann Ihr road_profile_viewer Package in sys.path nicht finden.

Warum es passiert:

Lösungen:

Option 1: Aus korrektem Verzeichnis ausführen

# Stellen Sie sicher, dass Sie im Projekt-Root sind:
cd /path/to/road-profile-viewer  # Das Verzeichnis, das road_profile_viewer/ enthält
python -m road_profile_viewer.main

Option 2: Projektstruktur überprüfen

ls
# Sollte sehen:
# road_profile_viewer/  ← Dies ist das Package
# pyproject.toml
# README.md
# etc.

Option 3: uv run verwenden (handhabt dies automatisch)

uv run python -m road_profile_viewer.main

9.3 Fehler 3: ImportError: cannot import name 'find_intersection' from 'geometry'

Wann Sie es sehen:

from geometry import find_intersection
ImportError: cannot import name 'find_intersection' from 'geometry'

Was ist passiert:

Python hat ein geometry-Modul gefunden, aber es hat nicht find_intersection.

Warum es passiert:

Lösungen:

Option 1: Vollständigen Import-Pfad verwenden

# Anstatt:
from geometry import find_intersection

# Verwenden:
from road_profile_viewer.geometry import find_intersection

Option 2: Auf Namenskonflikte prüfen

# Stellen Sie sicher, dass Sie nicht mehrere geometry.py Dateien haben:
find . -name "geometry.py"

Option 3: Überprüfen, dass die Funktion existiert

# Prüfen, was tatsächlich im Modul ist:
from road_profile_viewer import geometry
print(dir(geometry))

9.4 Fehler 4: ModuleNotFoundError: No module named 'numpy' (oder anderes externes Package)

Wann Sie es sehen:

import numpy as np
ModuleNotFoundError: No module named 'numpy'

Was ist passiert:

numpy ist nicht in Ihrer virtuellen Umgebung installiert.

Warum es passiert:

Lösungen:

Option 1: Abhängigkeiten mit uv synchronisieren

uv sync

Option 2: Package manuell installieren

uv add numpy

Option 3: Prüfen, welches Python Sie verwenden

which python  # Mac/Linux
where python  # Windows

# Sollte auf .venv/bin/python oder .venv\Scripts\python.exe zeigen

Option 4: Immer uv run verwenden

uv run python -m road_profile_viewer.main

9.5 Fehler 5: Zirkulärer Import

Wann Sie es sehen:

ImportError: cannot import name 'function_a' from partially initialized module 'module_b'
(most likely due to a circular import)

Was ist passiert:

Zwei Module importieren voneinander, was eine Abhängigkeitsschleife erzeugt.

Beispiel:

# geometry.py
from road_profile_viewer.road import some_function

# road.py
from road_profile_viewer.geometry import find_intersection

Python kann keines der Module initialisieren, weil beide das andere initialisiert benötigen!

Lösungen:

Option 1: Code umstrukturieren (am besten)

Gemeinsame Abhängigkeiten in ein separates Modul verschieben:

# utils.py
def shared_function():
    pass

# geometry.py
from road_profile_viewer.utils import shared_function

# road.py
from road_profile_viewer.utils import shared_function

Option 2: Import in Funktion verschieben

# geometry.py
def calculate_something():
    # Hier importieren statt oben
    from road_profile_viewer.road import some_function
    return some_function()

Option 3: Type Hints mit TYPE_CHECKING verwenden

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    # Nur während Type-Checking importiert, nicht zur Laufzeit
    from road_profile_viewer.road import RoadProfile

9.6 Fehler 6: NameError: name 'find_intersection' is not defined

Wann Sie es sehen:

distance = find_intersection(x, angle, profile)
NameError: name 'find_intersection' is not defined

Was ist passiert:

Sie haben vergessen, die Funktion zu importieren!

Lösung:

# Fügen Sie den Import am Anfang der Datei hinzu:
from road_profile_viewer.geometry import find_intersection

# Dann verwenden Sie sie:
distance = find_intersection(x, angle, profile)

9.7 Debug-Checkliste

Wenn Sie auf Import-Fehler stoßen, prüfen Sie:

  1. ✅ Sind Sie im richtigen Verzeichnis? (Projekt-Root)
  2. ✅ Verwenden Sie python -m package.module anstatt python file.py?
  3. ✅ Ist Ihre virtuelle Umgebung aktiviert? (oder verwenden Sie uv run?)
  4. ✅ Sind Abhängigkeiten installiert? (führen Sie uv sync aus)
  5. ✅ Ist der Import-Pfad korrekt? (prüfen Sie Schreibweise, Package-Struktur)
  6. ✅ Verwenden Sie absolute Imports? (empfohlen für diesen Kurs)
  7. ✅ Existiert die Funktion/Klasse tatsächlich im Modul?

9.8 Wichtigste Erkenntnisse: Import-Fehler


10. Best Practices Zusammenfassung

Lassen Sie uns mit umsetzbaren Best Practices für Ihre Python-Projekte abschließen.

10.1 Import-Stil-Richtlinien

1. Verwenden Sie absolute Imports (Kursanforderung)

# ✅ GUT: Absolute Imports
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile
from road_profile_viewer.visualization import create_dash_app

# ❌ VERMEIDEN: Relative Imports (für diesen Kurs)
from .geometry import find_intersection
from .road import generate_road_profile
from .visualization import create_dash_app

2. Imports richtig gruppieren

# Standardbibliotheks-Imports
import os
import sys
from pathlib import Path

# Externe Package-Imports
import numpy as np
import pandas as pd
import dash
from dash import dcc, html

# Lokale Package-Imports
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile

3. Spezifische Imports verwenden

# ✅ GUT: Importieren Sie, was Sie brauchen
from road_profile_viewer.geometry import find_intersection, calculate_ray_line

# ⚠️ OK aber weniger klar:
import road_profile_viewer.geometry
# Dann verwenden: road_profile_viewer.geometry.find_intersection()

# ❌ VERMEIDEN: Stern-Imports
from road_profile_viewer.geometry import *  # Wer weiß, was dies importiert?

10.2 Projektstruktur-Best-Practices

1. Halten Sie __init__.py minimal

# road_profile_viewer/__init__.py
"""Road Profile Viewer Package."""

__version__ = "1.0.0"

# Nur Haupt-API verfügbar machen
from road_profile_viewer.visualization import create_dash_app

__all__ = ['create_dash_app']

2. Nach Funktionalität organisieren

road_profile_viewer/
├── __init__.py
├── main.py              # Einstiegspunkt
├── core/                # Kernlogik
   ├── __init__.py
   ├── geometry.py
   └── road.py
├── ui/                  # Benutzeroberfläche
   ├── __init__.py
   └── visualization.py
└── utils/               # Hilfsprogramme
    ├── __init__.py
    └── helpers.py

3. Überschatten Sie niemals Standardbibliotheksnamen

# ❌ SCHLECHT: Namenskonflikte
json.py       # Überschattet eingebautes json
math.py       # Überschattet eingebautes math
sys.py        # Überschattet eingebautes sys
os.py         # Überschattet eingebautes os

# ✅ GUT: Beschreibende, eindeutige Namen
json_parser.py
math_utils.py
system_info.py
os_helpers.py

10.3 Code-Ausführungs-Best-Practices

1. Verwenden Sie immer -m für Package-Module

# ✅ GUT: Als Modul ausführen
python -m road_profile_viewer.main

# ❌ SCHLECHT: Als Skript ausführen (bricht relative Imports)
python road_profile_viewer/main.py

2. uv run für Bequemlichkeit verwenden

# ✅ AM BESTEN: Lassen Sie uv alles handhaben
uv run python -m road_profile_viewer.main

# ✅ AUCH GUT: Skripte ausführen, die in pyproject.toml definiert sind
uv run road-viewer

3. Vom Projekt-Root ausführen

# Führen Sie Befehle immer aus dem Verzeichnis aus, das Ihr Package enthält:
/path/to/road-profile-viewer/  ← Hier ausführen
├── road_profile_viewer/       ← Ihr Package
├── pyproject.toml
└── README.md

10.4 Abhängigkeitsmanagement-Best-Practices

1. Verwenden Sie pyproject.toml als einzige Quelle der Wahrheit

[project]
name = "road-profile-viewer"
version = "1.0.0"
dependencies = [
    "dash>=2.14.0",
    "plotly>=5.18.0",
    "pandas>=2.1.0",
    "numpy>=1.26.0",
]

2. Trennen Sie Entwicklungsabhängigkeiten

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "ruff>=0.1.0",
    "pyright>=1.1.0",
]

3. Verwenden Sie Versionsbeschränkungen weise

dependencies = [
    "numpy>=1.26.0",        # ✅ Patch/Minor-Updates erlauben
    "dash>=2.14,<3.0",      # ✅ Major-Version sperren
    "pandas==2.1.0",        # ⚠️ Zu streng, vermeiden Sie es, wenn nicht notwendig
]

10.5 Ihre Imports testen

Erstellen Sie ein Test-Skript, um zu überprüfen, dass alles funktioniert:

# test_imports.py
"""Überprüfen, dass alle Imports funktionieren."""

def test_imports():
    """Testen, dass alle Haupt-Imports funktionieren."""
    from road_profile_viewer.geometry import find_intersection
    from road_profile_viewer.road import generate_road_profile
    from road_profile_viewer.visualization import create_dash_app

    print("✅ Alle Imports erfolgreich!")

    # Überprüfen, dass Funktionen aufrufbar sind
    assert callable(find_intersection)
    assert callable(generate_road_profile)
    assert callable(create_dash_app)

    print("✅ Alle Funktionen sind aufrufbar!")

if __name__ == "__main__":
    test_imports()

Ausführen:

uv run python -m test_imports

10.6 Dokumentations-Best-Practices

1. Modulzweck dokumentieren

# geometry.py
"""
Geometrische Berechnungen für Strahl-Linien-Schnittpunkte.

Dieses Modul bietet Funktionen zur Berechnung von Schnittpunkten zwischen
Strahlen und Straßenprofil-Liniensegmenten.
"""

2. Import-Abhängigkeiten dokumentieren

# visualization.py
"""
Interaktive Dash-Webanwendung zur Straßenprofil-Visualisierung.

Abhängigkeiten:
    - dash: Webanwendungs-Framework
    - plotly: Interaktives Plotting
    - road_profile_viewer.geometry: Strahl-Schnittberechnungen
    - road_profile_viewer.road: Straßenprofil-Generierung
"""

10.7 Schnellreferenz: Import-Muster

Ihre eigenen Module:

# ✅ Verwenden Sie absolute Imports
from road_profile_viewer.geometry import find_intersection
from road_profile_viewer.road import generate_road_profile

Externe Packages:

# ✅ Einfache Imports
import numpy as np
import pandas as pd

Standardbibliothek:

# ✅ Einfache Imports
import os
import sys
from pathlib import Path

Ihren Code ausführen:

# ✅ Als Modul (vom Projekt-Root)
uv run python -m road_profile_viewer.main

# ✅ Skripte verwenden, die in pyproject.toml definiert sind
uv run road-viewer

10.8 Abschließende Checkliste für die Aufgabe

Vor der Abgabe Ihres refaktorierten road-profile-viewer:

10.9 Wichtigste Erkenntnisse: Best Practices


11. Fazit: Sie verstehen jetzt Pythons Import-System

Herzlichen Glückwunsch! Sie haben eine umfassende Reise durch Pythons Import-System abgeschlossen. Lassen Sie uns zusammenfassen, was Sie gelernt haben:

11.1 Was Sie jetzt wissen

  1. Module: Jede .py-Datei ist ein Modul mit eigenem Namespace
  2. Packages: Verzeichnisse mit Modulen, hierarchisch organisiert
  3. __init__.py: Optional in Python 3.3+, aber nützlich für Initialisierung und bequeme Imports
  4. Absolute Imports: Verwenden vollständige Pfade vom Package-Root (from road_profile_viewer.geometry import ...)
  5. Relative Imports: Verwenden Punkte zur Navigation in Package-Hierarchie (. = aktuell, .. = Eltern)
  6. Externe Packages: Installiert in site-packages/, importierbar mit einfachen Namen (import numpy)
  7. sys.path: Pythons Suchpfad bestimmt, wo Module gefunden werden
  8. Häufige Fehler: Wie man Import-Probleme diagnostiziert und behebt
  9. Best Practices: Richtlinien für professionelle Python-Projekte

11.2 Warum das für Ihre Karriere wichtig ist

Das Verständnis von Pythons Import-System ist fundamental, weil:

11.3 Wie dies auf Ihre Aufgabe zutrifft

Für die road-profile-viewer Refactoring-Aufgabe:

  1. ✅ Verwenden Sie absolute Imports ausschließlich: from road_profile_viewer.module import function
  2. ✅ Führen Sie mit python -m road_profile_viewer.main oder uv run python -m road_profile_viewer.main aus
  3. ✅ Halten Sie Ihre Package-Struktur flach (keine Unterverzeichnisse nötig)
  4. ✅ Optional fügen Sie __init__.py für Package-Metadaten und bequeme Imports hinzu
  5. ✅ Stellen Sie sicher, dass alle Imports funktionieren, indem Sie mit uv sync && uv run python -m road_profile_viewer.main testen

11.4 Weiterführende Literatur (optional)

Wenn Sie tiefer in Pythons Import-System eintauchen möchten:

11.5 Ressourcen für die Aufgabe

11.6 Praxisübungen (optional)

Möchten Sie Ihr Verständnis festigen? Versuchen Sie diese Übungen:

Übung 1: Weitere Module hinzufügen

Erstellen Sie ein neues Modul road_profile_viewer/config.py mit Konfigurationskonstanten:

# config.py
"""Konfigurationskonstanten für die Anwendung."""

DEFAULT_NUM_POINTS = 100
DEFAULT_RAY_ANGLE = 45
DEFAULT_PORT = 8050

Importieren und verwenden Sie es in Ihren anderen Modulen.

Übung 2: Ein Utilities-Package erstellen

Organisieren Sie Hilfsfunktionen in ein Sub-Package:

road_profile_viewer/
├── utils/
│   ├── __init__.py
│   ├── math_helpers.py
│   └── plotting_helpers.py

Übung 3: Package-Metadaten hinzufügen

Erstellen Sie ein umfassendes __init__.py mit Versionsinformationen, bequemen Imports und Logging-Setup.

Übung 4: Import-Stile testen

Erstellen Sie Branches in Ihrem Git-Repo zum Experimentieren:


12. Nächste Schritte

Jetzt, da Sie Pythons Import-System verstehen, sind Sie bereit:

  1. ✅ Ihr road-profile-viewer Refactoring mit Zuversicht abzuschließen
  2. ✅ Größere Python-Projekte professionell zu strukturieren
  3. ✅ Import-Fehler schnell und effektiv zu debuggen
  4. ✅ Mit Teams mit konsistenten Konventionen zusammenzuarbeiten
  5. ✅ Open-Source-Python-Codebasen zu lesen und zu verstehen

Viel Erfolg beim Programmieren, und mögen alle Ihre Imports erfolgreich aufgelöst werden! 🐍


Dieser Anhang ist Teil des Software Engineering Kurses an der Hochschule Aalen (Wintersemester 2025/26). Für Fragen oder Feedback wenden Sie sich an das Kursforum oder die Sprechstunden.

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk