# Python-Grundlagen und Skript-Automatisierung

## Inhaltsverzeichnis

- [Python-Installation und -Erste Schritte](#python-installation-und-erste-schritte)  
- [Einrückung und Codeblöcke](#einrückung-und-codeblöcke)  
- [Variablen und Datentypen](#variablen-und-datentypen)  
- [Kommentare](#kommentare)  
- [Zeichenketten (Strings)](#zeichenketten-strings)  
- [Listen (List)](#listen-list)  
- [Tupel (Tuple)](#tupel-tuple)  
- [Wörterbücher (Dictionaries)](#wörterbücher-dictionaries)  
- [Mengen (Sets)](#mengen-sets)  
- [Ein- und Ausgabe (Input/Output)](#ein--und-ausgabe-inputoutput)  
- [Entscheidungsstrukturen (if/elif/else)](#entscheidungsstrukturen-ifelifelse)  
- [Schleifen (for, while, break, continue)](#schleifen-for-while-break-continue)  
- [Funktionen](#funktionen)  
- [Module und Bibliotheken (`import`)](#module-und-bibliotheken-import)  
- [Dateiverarbeitung](#dateiverarbeitung)  
- [Fehler und Ausnahmebehandlung (Try/Except)](#fehler-und-ausnahmebehandlung-tryexcept)  
- [Programmablaufplan (PAP)](#programmablaufplan-pap)  
- [Beispielaufgaben und Lösungen](#beispielaufgaben-und-lösungen)  
- [Zusammenfassung / Spickzettel](#zusammenfassung--spickzettel)  

---

## Python-Installation und -Erste Schritte

Python ist eine interpretierte Programmiersprache. Man installiert Python (z. B. Version 3.x) auf dem Computer und kann dann Python-Skripte (.py-Dateien) schreiben. Diese Skripte werden in der Kommandozeile mit Befehlen wie `python meinprogramm.py` ausgeführt. In Windows kann man z. B. in der Eingabeaufforderung (CMD) mit `python --version` die installierte Python-Version abfragen. Oft legt man Skript-Dateien in einem Editor an (z. B. Notepad, VS Code) und speichert sie mit der Endung `.py`.

Beim Starten von Python in der Konsole (Interaktivmodus) sieht man eine Begrüßungsnachricht und kann Befehle direkt eingeben. Beispiel:
```bash
C:\Users\YourName> python
Python 3.10.0 (v3.10.0:abcde, Oct 20 2021, 10:00:00) [MSC v.1916 64 bit (AMD64)] on win32
>>> print("Hello, World!")   
Hello, World!
>>> exit()
```

Das erste Programm in Python ist oft einfach eine Ausgabe. **`print()`** gibt Text auf der Konsole aus. Zum Beispiel erzeugt `print("Hello, World!")` die Ausgabe **Hello, World!**. Um ein Skript zu beenden, schreibt man z.B. `exit()` oder nutzt Strg+Z (Windows) oder Strg+D (Linux/Mac).

**Wichtige Standardmodule**: Python wird mit vielen eingebaute Modulen ausgeliefert. Mit dem Keyword `import` kann man sie nutzen (z.B. `import datetime` oder `import sys`). Manche Aufgaben (siehe Übungen) erfordern das Importieren von Modulen wie `sys` (für Systeminformationen) oder `datetime` (für Datum/Uhrzeit).

## Einrückung und Codeblöcke

Python verwendet **Einrückung** (Whitespace) statt geschweifter Klammern zur Strukturierung des Codes. Das heißt, zusammengehörige Codeblöcke (wie Anweisungen nach `if`, `for` usw.) müssen durch Leerzeichen eingerückt sein. Wird beispielsweise eine Bedingung erfüllt, dann führt Python alle eingerückten Zeilen darunter aus. Beispiel:
```python
if 5 > 2:
    print("5 ist größer als 2")
```
Hier muss die Zeile `print(...)` unter dem `if` eingerückt sein, sonst gibt es einen Fehler. Python verlangt mindestens einen Leerraum (oft 4 Leerzeichen oder ein Tab) für jeden Block. Falsches Einrücken (z.B. wenn `print` nicht eingerückt wäre) führt zu einem **SyntaxError**. In anderen Sprachen dient Einrückung nur zur Lesbarkeit, in Python markiert sie aber **Codeblöcke**.

## Variablen und Datentypen

Eine **Variable** ist ein Name für einen Speicherplatz, in dem Daten (Zahlen, Texte, Listen etc.) gehalten werden. In Python muss man Variablen nicht vorher deklarieren. Man weist einfach einen Wert zu, und Python merkt sich den Typ automatisch. Beispiel:
```python
x = 5            # x enthält eine Ganzzahl (int)
y = "John"       # y enthält einen Text (String)
```
Variablen können den Typ wechseln, indem man einen anderen Wert zuweist (dynamische Typisierung):

```python
x = 4            # x ist int
x = "Sally"      # x ist jetzt ein String
print(x)         # Ausgabe: Sally
```
Um den Datentyp einer Variable zu erfahren, nutzt man die Funktion `type()`. Beispiel `print(type(x))` zeigt `<class 'str'>` für Strings, `<class 'int'>` für ganze Zahlen usw.

**Erlaubte Zeichen in Variablennamen**: Ein Variablenname darf Buchstaben (a–z, A–Z), Ziffern (0–9) und Unterstriche (_) enthalten, darf aber nicht mit einer Ziffer beginnen. Auch Groß- und Kleinschreibung wird unterschieden (z.B. `age` und `Age` sind verschiedene Variablen). Beispiel für gültige Namen: `my_var`, `_temp`, `value2`. Ein falsches Beispiel wäre `2ndValue`, weil es mit `2` beginnt.

**Datentypen**: Die wichtigsten eingebauten Datentypen sind:
- **int** – ganze Zahlen (z.B. 5, -3)
- **float** – Kommazahlen (z.B. 3.14)
- **str** – Zeichenketten (Text), in Anführungszeichen (z.B. `"Hallo"`)
- **bool** – Wahrheitswerte `True` oder `False`
- **list** – Listen (Sammlung von Werten, geordnet, veränderbar)
- **tuple** – Tupel (Sammlung geordneter Werte, unveränderbar)
- **dict** – Wörterbuch (Key:Value-Paare)
- **set** – Menge (ungeordnete Sammlung ohne Duplikate)

Beispiel für verschiedene Typen:
```python
a = 10             # int
b = 3.14           # float
c = "Python"       # str
d = True           # bool
e = [1,2,3]        # list
f = (4,5,6)        # tuple
g = {"x": 1}       # dict
h = {7,8,9}        # set
```

*Typumwandlung (Casting)*: Mit Funktionen wie `int()`, `str()`, `float()` kann man Werte umwandeln. Beispiel: `int("8")` ergibt die Zahl 8. In Python kann man nicht ohne Weiteres eine Zahl und einen String mit `+` verbinden (das führt zu einem Fehler, z.B. `5 + "Hello"` gibt `TypeError` zurück). Man muss ggf. explizit konvertieren.

## Kommentare

Kommentare dienen nur zur Erklärung und werden von Python ignoriert. Sie beginnen mit `#` für einzeilige Kommentare. Beispiel:

```python
# Dies ist ein Kommentar
print("Hello, World!")  # Auch hinter Code möglich
```

Für mehrzeilige Kommentare kann man dreifache Anführungszeichen verwenden (fälschlicherweise oft als mehrzeilige Kommentare genutzt, technisch sind das String-Literale, die nicht zugewiesen werden):
```python
"""
Dies ist ein mehrzeiliger Kommentar
oder eine Dokumentation (Docstring),
wird im Code über mehrere Zeilen verteilt.
"""
```

Kommentare helfen, den Code zu dokumentieren und zu strukturieren. Sie sind wichtig, um die Lesbarkeit zu verbessern, beeinflussen aber nicht die Programmlogik.

## Zeichenketten (Strings)

Ein **String** in Python ist eine Folge von Zeichen, umgeben von Anführungszeichen. Man kann einfache (`'...'`) oder doppelte (`"..."`) Anführungszeichen verwenden (beide Varianten sind gleichwertig). Beispiel:
```python
text = "Hallo"
text2 = 'Welt'
```

Strings sind unveränderlich (immutable) – alle Methoden, die einen neuen String zurückgeben, verändern das Original nicht. Wichtige Eigenschaften und Methoden von Strings sind:
- **Konkatenation**: Mehrere Strings mit `+` verbinden, z.B. `"Hallo " + "Welt!"` ergibt `"Hallo Welt!"`.
- **Indexierung/Slicing**: Man kann einzelne Zeichen oder Teilstücke (`substring`) mittels Indexzugriff erhalten, z.B. `text[0]` gibt `'H'` (erstes Zeichen), `text[1:3]` gibt `'al'`.
- **Methoden**: 
  - `count(sub)`: Zählt, wie oft `sub` in String vorkommt.
  - `endswith(suf)`: Prüft, ob der String mit `suf` endet.
  - `find(sub)` / `index(sub)`: Sucht nach `sub` und gibt den Index zurück (oder -1 bzw. Fehler, wenn nicht gefunden).
  - `isnumeric()`: Prüft, ob alle Zeichen Ziffern sind.
  - `lower()`, `upper()`: Konvertieren in Klein-/Großbuchstaben.
  - `split(sep)`: Teilt den String an jedem `sep` und liefert eine Liste von Teilstrings.
  - `strip()`, `lstrip()`, `rstrip()`: Entfernen Leerzeichen etc. (Trimmen).
  - `replace(old, new)`: Ersetzt Teilstring.
  
*Beispiel:*

```python
s = "Hallo Welt"
print(s.upper())       # "HALLO WELT"
print(s.count("l"))    # Anzahl 'l' im String
print(s.split(" "))    # ['Hallo', 'Welt']
print("123".isnumeric())  # True
```

## Listen (List)

**Listen** sind geordnete, veränderbare Sammlungen. Man definiert sie mit eckigen Klammern:

```python
fruits = ["Apfel", "Banane", "Kirsche"]
numbers = [1, 2, 3, 5, 8]
```

Man kann Listen beliebig mischen: `mixed = [1, "Hallo", 3.14, True]`. Elemente in einer Liste haben einen Index (beginnend bei 0): `fruits[0]` liefert `"Apfel"`. Listen können verschachtelt sein (z.B. `nested = [1, [2,3], 4]`).

Wichtige Methoden für Listen:  
- `append(x)`: Fügt Element `x` ans Ende hinzu.  
- `remove(x)`: Entfernt das erste Vorkommen des Werts `x`.  
- `count(x)`: Zählt, wie oft `x` in der Liste vorkommt.  
- `reverse()`: Kehrt die Reihenfolge der Liste um.  
- `sort()`: Sortiert die Liste (Zahlen aufsteigend, Strings alphabetisch).  
- `extend(other_list)`: Hängt eine andere Liste an.
- Zugriff: `len(list)` gibt die Länge; `list[index]` greift zu.

**Beispiel:**

```python
a = [1,1,2,3,5,8,13,21,34]
a.append(55)
print(a[-1])        # Letztes Element: 55
a.remove(1)         # Entfernt eine '1'
print(a.count(1))   # Anzahl der 1en
a.sort()            # Sortiert die Liste
a.reverse()         # Kehrt die Reihenfolge um
```

*Listenverständnis (List Comprehension)*: Man kann auch neue Listen aus bestehenden in einer Zeile erstellen. Beispiel für alle Elemente < 5: `new_list = [x for x in a if x < 5]`. Das entspricht grob einer kurzen Schleife, ist aber sehr kompakt.

## Tupel (Tuple)

Ein **Tupel** ist ähnlich wie eine Liste, aber *unveränderlich* (immutable). Man definiert es mit runden Klammern:
```python
t = (1, 2, 3)
```

Ein Tupel kann nicht nachträglich verändert werden: Man kann Elemente nicht hinzufügen oder entfernen (da kein `append()` oder `remove()` verfügbar ist). Tupel eignen sich, wenn eine feste Reihenfolge von Werten benötigt wird. Zugriff per Index geht genauso (z.B. `t[1]` ergibt `2`). Man kann allerdings ein Tupel **auspacken** (unpacking):

```python
t = ("Apfel", "Banane", "Kirsche")
x, y, z = t      # x="Apfel", y="Banane", z="Kirsche"
```

Auch erlaubt: "`x, y, z = fruits`" für eine Liste statt eines Tupels. Ein einzelnes Tupel-Element schreibt man mit Komma, z.B. `single = ("Einzel",)`. Ein Tupel ohne Komma `(x)` wäre nur `x` in Klammern.

## Wörterbücher (Dictionaries)

Ein **Dictionary** speichert Werte als Paare Schlüssel:Wert. In Python nutzt man geschweifte Klammern `{}`. Beispiel:
```python
person = {"Name": "Alice", "Alter": 30, "Ort": "Zürich"}
```

- **Zugriff**: `person["Name"]` liefert `"Alice"`.  
- **Hinzufügen**: `person["Beruf"] = "Lehrer"` fügt ein neues Schlüssel-Wert-Paar hinzu.  
- **Ändern**: `person["Alter"] = 31` überschreibt den Wert zu Schlüssel `"Alter"`.  
- **Entfernen**: `del person["Ort"]` löscht das Element mit Schlüssel `"Ort"`.  
- **Methoden**:  
  - `keys()`: Gibt alle Schlüssel als Liste (oder View-Objekt).  
  - `values()`: Gibt alle Werte.  
  - `items()`: Gibt `(Schlüssel, Wert)`-Paare als Liste von Tupeln.  
  - `get(key)`: Gibt den Wert für `key` oder `None` (bzw. einen Standardwert) zurück.  
  - `pop(key)`: Entfernt und gibt den Wert zu `key` zurück.
  - `update(other_dict)`: Fügt mehrere Paare aus einem anderen Dictionary hinzu.

**Beispiel:**

```python
d = {"x": 10, "y": 20}
print(d.keys())     # Ausgabe: dict_keys(['x', 'y'])
print(d.values())   # Ausgabe: dict_values([10, 20])
d["z"] = 30         # neues Paar hinzufügen
d.update({"w": 40}) # mehrere Paare
```

Dictionaries sind **geordnet** in der Reihenfolge, in der Elemente hinzugefügt wurden (seit Python 3.7 garantiert).

## Mengen (Sets)

Ein **Set** (Menge) ist eine ungeordnete Sammlung ohne Duplikate. Man definiert es mit `{}` oder `set()`:

```python
s = {1, 2, 3, 3, 2}
print(s)  # Ausgabe: {1, 2, 3}
```

Das Set enthält keine Duplikate – doppelte Werte werden automatisch entfernt. Element-Reihenfolge ist nicht garantiert. Sets eignen sich gut für Mengenoperationen (Schnitt, Vereinigung, Differenz).

- **Hinzufügen**: `s.add(x)` fügt ein Element hinzu.  
- **Entfernen**: `s.remove(x)` oder `s.discard(x)` entfernt ein Element (bei `remove(x)` gibt es einen Fehler, wenn `x` nicht da ist, `discard(x)` gibt keinen Fehler).  
- **Schnittmenge**: `s1.intersection(s2)` oder `s1 & s2` ergibt neue Menge mit gemeinsamen Elementen.  
- **Vereinigung**: `s1.union(s2)` oder `s1 | s2` vereint Mengen.  
- **Differenz**: `s1.difference(s2)` oder `s1 - s2` ergibt Elemente, die in s1 aber nicht in s2 sind.  
- **Übrige Operationen**: `isdisjoint()`, `issubset()`, `issuperset()`, `symmetric_difference()`, usw..

*Beispiel:*

```python
a = {1,2,3,4}
b = {3,4,5}
print(a & b)  # Schnittmenge: {3,4}
print(a | b)  # Vereinigung: {1,2,3,4,5}
print(a - b)  # Differenz: {1,2}
```

## Ein- und Ausgabe (Input/Output)

**Ausgabe**: Die Funktion `print()` gibt Text bzw. Werte auf dem Bildschirm (Konsole) aus. Mehrere Argumente trennt `print` automatisch durch ein Leerzeichen, oder man formatiert mit `+`. Beispiel: `print("Python ist", "cool")` oder `print("Ergebnis:", x+y)`. Zum besseren Formatieren kann man **f-Strings** verwenden, z.B. `print(f"Wert ist {x}")` (seit Python 3.6).

**Eingabe**: Die Funktion `input()` hält das Programm an und wartet, bis der Benutzer etwas eingibt (Eingabeaufforderung). Beispiel:
```python
name = input("Gib deinen Namen ein: ")
print("Hallo", name)
```
Die `input()`-Funktion gibt immer einen String zurück. Möchte man eine Zahl eingeben, muss man diesen String in einen numerischen Typ umwandeln, z.B. `int(input("Zahl: "))` für ganze Zahlen oder `float(input(...))` für Dezimalzahlen. Beispiel aus [43]:

```python
print("Wie heißt du?")
name = input()    # Warten auf Benutzereingabe (hier ohne Prompt-Parameter)
print(f"Hallo {name}!")
```

Für Datum und Uhrzeit benutzt man das Modul `datetime`:
```python
from datetime import datetime
jetzt = datetime.now()
print("Heutiges Datum:", jetzt.date())     # Datum
print("Uhrzeit:", jetzt.time().strftime("%H:%M:%S"))  # Uhrzeit
```

*Beispielübung (Input):* Eine häufige Übung ist, Kommaseparierte Zahlen einzulesen:
```python
nums = input("Gib Zahlen ein, getrennt mit Komma: ")
liste = nums.split(",")   # String teilen bei Komma
liste = [int(x) for x in liste]  # in ints umwandeln
print(liste)
```

## Entscheidungsstrukturen (if/elif/else)

Mit `if`, `elif` und `else` steuert man bedingte Programmabläufe. Bedingungen sind Vergleiche (==, !=, >, <, >=, <=) oder logische Kombinationen. Python kennt boolesche Werte `True` und `False`. Beispiele von Vergleichsoperatoren:

```
a == b   # gleich?  
a != b   # ungleich?  
a < b, a > b, a <= b, a >= b
```

Man verwendet diese meist in `if`-Anweisungen:
```python
if bedingung:
    # Codeblock, wenn True
elif andere_bedingung:
    # Code, wenn erste False, zweite True
else:
    # Code, wenn keine Bedingung erfüllt
```

**Wichtige Punkte**:  
- Die Bedingung nach `if`/`elif` muss in Klammern oder ohne Klammern angegeben sein (Klammern sind optional).  
- Nach `if` und `elif` kommt ein Doppelpunkt `:` und der jeweilige Codeblock wird eingerückt geschrieben.  
- Man kann beliebig viele `elif` einfügen, oder auch nur ein `else` ohne `elif`.  
- `else` fängt *alle anderen* Fälle ab, wenn keine vorherige Bedingung zutrifft.

*Beispiel*:

```python
x = 10
if x < 5:
    print("x ist klein")
elif x == 5:
    print("x ist 5")
else:
    print("x ist größer als 5")
```
In diesem Fall wird `"x ist größer als 5"` ausgegeben, weil weder `x<5` noch `x==5` zutrifft. Das `else` fängt das auf. 

## Schleifen (for, while, break, continue)

### `for`-Schleife  

Die `for`-Schleife iteriert über alle Elemente einer **Sequenz** (Liste, Tupel, String, etc.). Sie ist besonders nützlich, wenn man vorab weiß, über welche Elemente man laufen möchte. Beispiel:
```python
fruits = ["Apfel", "Banane", "Kirsche"]
for f in fruits:
    print(f)
```
Das gibt alle Früchte untereinander aus. Python-`for` benötigt keinen Zähler; die Schleife geht direkt durch die Elemente der Liste (wie in vielen Objekt-orientierten Sprachen üblich).

Man kann auch über Zeichenketten oder Bereiche iterieren. Beispiel:```python
for buchstabe in "Hallo":
    print(buchstabe)
```
Oder mit `range(start, stop, step)` für Zahlenfolgen:
```python
for i in range(1, 6):  # 1 bis 5
    print(i)
```

### `while`-Schleife  

Die `while`-Schleife wiederholt Anweisungen **solange** eine Bedingung wahr ist. Man benutzt sie, wenn man nicht genau weiß, wie oft etwas wiederholt werden soll, sondern eine Bedingung den Abbruch bestimmt:
```python
i = 1
while i < 6:
    print(i)
    i += 1
```
In diesem Beispiel wird `i` von 1 bis 5 ausgegeben. Wichtig: Man muss darauf achten, dass die Bedingung irgendwann falsch wird, sonst läuft man in eine Endlosschleife (in obigem Beispiel erhöht sich `i` in jedem Schritt). Daher oft der Hinweis: Ohne Erhöhung der Schleifenvariable würde die Schleife endlos laufen.

### `break` und `continue`  

- **`break`** beendet die gesamte Schleife sofort, auch wenn die Bedingung noch wahr wäre. Beispiel:
  ```python
  i = 1
  while i < 6:
      print(i)
      if i == 3:
          break
      i += 1
  ```
  Dieses Beispiel stoppt, sobald `i` den Wert 3 erreicht – danach bricht die Schleife ab.  
- **`continue`** überspringt den Rest des Schleifenblocks für diese Iteration und fährt mit der nächsten Iteration fort. Beispiel:
  ```python
  i = 0
  while i < 6:
      i += 1
      if i == 3:
          continue
      print(i)
  ```
  Hier wird die Schleife beim Wert 3 nicht `print` ausgeführt, sondern direkt zur nächsten Runde gesprungen, sodass die Ausgabe 1, 2, 4, 5, 6 ist.

### `else` nach Schleifen  

Sowohl `for`- als auch `while`-Schleifen können optional ein `else:`-Block haben, der ausgeführt wird, wenn die Schleife **normal beendet** wird (nicht durch `break`). Beispiel:
```python
i = 1
while i < 4:
    print(i)
    i += 1
else:
    print("Schleife beendet")
```
Hier wird nach dem Beenden der while-Schleife die Nachricht `"Schleife beendet"` ausgegeben (weil kein `break` kam).

## Funktionen

Funktionen fassen Code zu wiederverwendbaren Blöcken zusammen. Man definiert sie mit `def` gefolgt vom Funktionsnamen und Klammern. Beispiel:
```python
def meine_funktion():
    print("Dies ist eine Funktion")
```
Dieses Beispiel definiert eine Funktion namens `meine_funktion`, die beim Aufruf den Text ausgibt. Den Code in der Funktion schreibt man eingerückt. Um die Funktion auszuführen, schreibt man später `meine_funktion()`.
```python
meine_funktion()  # Aufruf der Funktion
```

Funktionen können **Parameter** entgegennehmen:
```python
def begruessen(name):
    print(f"Hallo, {name}!")
```
Hier hat die Funktion `begruessen` einen Parameter `name`. Beim Aufruf kann man z.B. `begruessen("Anna")` machen, dann wird `"Hallo, Anna!"` ausgegeben. Man kann beliebig viele Parameter angeben (z.B. `def add(a, b): return a + b`).

**Rückgabewert (`return`)**: Mit `return` kann eine Funktion einen Wert zurückliefern. Sobald Python auf `return` stößt, verlässt die Funktion. Beispiel:
```python
def quadriere(x):
    return x * x

result = quadriere(5)
print(result)   # Ausgabe: 25
```
Wenn eine Funktion kein `return` enthält, liefert sie implizit `None` zurück. Man kann `return` auch nutzen, um komplexere Werte wie Listen oder Wörterbücher zurückzugeben. Beispiel aus [41]:

```python
def fahrenheit_to_celsius(fahrenheit):
    return (fahrenheit - 32) * 5 / 9

print(fahrenheit_to_celsius(77))  # Werte direkt zurückgeben und ausdrucken
```

**`pass`-Statement**: Eine Funktion kann leer definiert werden (z.B. Platzhalter), wenn man erst später implementiert:

```python
def placeholder():
    pass
```
Das `pass` verhindert Syntaxfehler, indem es eine leere Operation repräsentiert. 

## Module und Bibliotheken (`import`)

Python-Code kann in mehrere Dateien (Module) aufgeteilt werden. Man lädt diese Dateien mit `import`. Zum Beispiel enthält das Modul `sys` Systeminformationen, `datetime` Datumsfunktionen. Beispiel für das importieren und benutzen von Modulen:
```python
import sys
print(sys.version)            # Gibt Python-Versionsinfo aus

from datetime import datetime
jetzt = datetime.now()
print(jetzt)                  # Aktuelles Datum/Uhrzeit
```

Viele Standardaufgaben (Datum, Zufallszahlen, mathematische Funktionen) haben eingebaute Module. Z.B. `import math` für trigonometrische Funktionen, `import random` für Zufallszahlen. Um zusätzliche Bibliotheken zu verwenden, muss man diese eventuell per `pip install` installieren.

## Dateiverarbeitung

Ein wichtiger Bestandteil der Systemadministration ist das Lesen und Schreiben von Dateien. In Python nutzt man dafür die Funktion `open()`. Die Funktionsweise:
```python
f = open("datei.txt", "r")
text = f.read()
f.close()
```

- Der erste Parameter von `open()` ist der Dateiname, der zweite ist der **Modus**:
  - `"r"` (read): Datei zum Lesen öffnen (Standard). Fehler, wenn Datei nicht existiert.  
  - `"w"` (write): Datei zum Schreiben öffnen. Ist sie nicht da, wird sie neu erstellt; existiert sie, wird sie überschrieben.  
  - `"a"` (append): Datei zum Anhängen öffnen. Neues Schreiben wird an das Ende angehängt; Datei wird erstellt, falls sie nicht existiert.  
  - `"x"` (create): Datei erstellen; Fehler, wenn sie bereits existiert.  
  - "`t`" oder "`b`": Mit `"t"` (Text) oder `"b"` (Binary) kann man Text- vs. Binärmodus angeben (z.B. `open("bild.jpg","rb")`). Standard ist Textmodus.

Nach dem Öffnen kann man lesen oder schreiben:  
- `f.read()` liest den gesamten Inhalt als String.  
- `f.readline()` liest eine Zeile.  
- `f.readlines()` liefert eine Liste aller Zeilen.  
- `f.write("Text")` schreibt Text (Achtung: bei `"w"`-Modus überschreibt es die Datei, bei `"a"` hängt es an).  

Besser ist der **with-Block**:  
```python
with open("datei.txt", "r") as f:
    content = f.read()
# f ist hier automatisch geschlossen
```
Das schließt die Datei automatisch, auch wenn Fehler auftreten.  

**Beispiel**: Schreibe eine Liste in eine Datei:

```python
zahlen = [1,2,3]
with open("zahlen.txt", "w") as f:
    for num in zahlen:
        f.write(f"{num}\n")  # schreib jede Zahl in neue Zeile
```

## Fehler und Ausnahmebehandlung (Try/Except)

Bei Fehlern stoppt Python standardmäßig das Programm. Beispiele: Syntaxfehler (z.B. fehlender Doppelpunkt) beim *Start* (werden als `SyntaxError` gemeldet), oder Laufzeitfehler (z.B. Division durch Null, Dateifehler) während der Ausführung. Man unterscheidet oft:
- **Syntaxfehler**: Verstoß gegen die Sprachregeln (z.B. `if 5>2` ohne Doppelpunkt). Werden meist schon beim Übersetzen erkannt.  
- **Laufzeitfehler**: Passieren bei der Ausführung (z.B. `ZeroDivisionError`, `TypeError`, Indexfehler).  
- **Semantische (logische) Fehler**: Der Code läuft, liefert aber falsches Ergebnis wegen fehlerhafter Logik. Diese findet man durch Tests.

Um Laufzeitfehler abzufangen, benutzt man **try/except**:  
```python
try:
    # Code, der Fehler erzeugen könnte
    erg = 10 / 0
except ZeroDivisionError:
    print("Division durch Null ist nicht erlaubt")
except Exception as e:
    print("Ein anderer Fehler ist aufgetreten:", e)
else:
    print("Kein Fehler, Ergebnis:", erg)
finally:
    print("Dieser Block läuft immer") 
```

- Das `try`-Block umschließt kritischen Code.  
- Bei einem Fehler springt Python zum entsprechenden `except`-Block. Ohne `try` würde das Programm abstürzen.  
- `else` (optional) läuft, wenn kein Fehler auftrat.  
- `finally` (optional) läuft immer, egal ob Fehler kam oder nicht (z.B. zum Aufräumen).
- In obigem Beispiel würde die Division in einen `ZeroDivisionError` münden, also wird der erste `except` ausgeführt. Wäre alles gut, käme der `else` dran.  

Dieses Verfahren verbessert die Fehlertoleranz des Programms, indem man gezielt auf mögliche Fehler reagiert (z.B. Benutzerwarnung bei falscher Eingabe).

## Programmablaufplan (PAP)

Ein **Programmablaufplan** (PAP), auch Flussdiagramm genannt, ist eine grafische Darstellung des Programmablaufs. Er hilft beim Entwerfen von Algorithmen: Man zeichnet als Startpunkt ein Symbol (z.B. ein abgerundetes Rechteck), dann nutzt man Entscheidungsdiamanten für `if`-Bedingungen, Pfeile für Flussrichtung und rechteckige Aktionen für Befehle. Der Plan zeigt Schritt für Schritt, wie Daten verarbeitet werden. PAP ist eine Planungsmethode (Pseudo-Code / Strukturplan), die hilft, das Programm vor dem Schreiben klar zu strukturieren. [Beispiel-Tools im Unterricht können Diagramm-Programme sein.] Man schreibt z.B. erst als PAP „Lies Vor- und Nachname ein“, dann „Drucke reversed name“, bevor man es in Code umsetzt.

## Beispielaufgaben und Lösungen

Hier einige Beispielaufgaben, wie sie auch im Unterricht vorkamen, inklusive Lösungsideen:

1. **Verwendete Python-Version ermitteln (Import System)**:  
   ```python
   import sys
   print(sys.version)  # Gibt die verwendete Python-Version aus
   ```  
   Alternativ kann man auch `import platform; print(platform.python_version())` nutzen, je nach Aufgabe.  

2. **Aktuelles Datum und Uhrzeit anzeigen**:  
   ```python
   from datetime import datetime
   now = datetime.now()
   print("Datum:", now.date())
   print("Zeit:", now.strftime("%H:%M:%S"))
   ```

3. **Namen umkehren**: Programm nimmt Vor- und Nachnamen vom Benutzer und gibt sie in umgekehrter Reihenfolge aus:  
   ```python
   vorname = input("Vorname: ")
   nachname = input("Nachname: ")
   print(f"{nachname} {vorname}")  # Ausgabe z.B. "Meier Anna"
   ```

4. **Comma-Separated List zu Liste und Tuple**: Eingabe z.B. `"3, 5, 7, 23"` und Ausgabe:
   ```python
   nums = input("Zahlen, getrennt durch Kommata: ")
   liste = [x.strip() for x in nums.split(",")]  # ["3", "5", "7", "23"]
   tupel = tuple(liste)
   print("Liste :", liste)
   print("Tuple :", tupel)
   ```
   Hier teilen wir den Eingabestring an den Kommas und entfernen ggf. Leerzeichen. Die Liste wird dann in ein Tupel umgewandelt.  

5. **Dateierweiterung aus Dateinamen extrahieren**: Eingegebener Dateiname `"abc.java"`, Ausgabe `"java"`:  
   ```python
   fname = input("Dateiname mit Endung: ")
   parts = fname.split(".")
   if len(parts) > 1:
       ext = parts[-1]
   else:
       ext = ""
   print("Erweiterung:", ext)
   ```
   Alternativ kann man `ext = fname.rsplit(".",1)[-1]` schreiben (Teile nur am letzten Punkt).  

6. **Elemente aus Liste filtern (< 5)**: Gegeben `a = [1,1,2,3,5,8,13,...]`. Programmieraufgabe:
   - **Ohne Liste**: Schleife, die jedes Element prüft:  
     ```python
     for e in a:
         if e < 5:
             print(e)
     ```  
   - **Neue Liste mit Comprehension**:  
     ```python
     unter5 = [e for e in a if e < 5]
     print(unter5)  # z.B. [1, 1, 2, 3]
     ```  
   - **Einzeiler** (List Comprehension): wie oben.  
   - **Mit Benutzereingabe**: Wenn man eine Zahl `n` eingibt und nur Elemente < `n` haben möchte:  
     ```python
     n = int(input("Schwellenwert: "))
     result = [e for e in a if e < n]
     print(result)
     ```  

7. **Gemeinsame Elemente zweier Listen (ohne Duplikate)**: Gegeben  
   ```python
   a = [1,1,2,3,5,8,13,21,34,55,89]
   b = [1,2,3,4,5,6,7,8,9,10,11,12,13]
   ```  
   Rückgabe: eine Liste mit den gemeinsamen Werten (z.B. `[1,2,3,5,8,13]`). Eine Lösung:  
   ```python
   gemeinsame = [x for x in set(a) if x in b]
   print(gemeinsame)
   ```  
   - Hier wandeln wir `a` in ein `set(a)` um, um Duplikate zu entfernen. Dann prüfen wir für jedes Element, ob es in `b` ist.  
   - Oder mit Mengen:  
     ```python
     gemeinsame_set = set(a).intersection(b)
     print(list(gemeinsame_set))
     ```  

8. **Liste mit Namen erstellen**: (Aufgabe vermutlich, um mit Listen umzugehen – z.B. z.B. `names = []` und dann Elemente mit `append` hinzufügen). Beispiel:  
   ```python
   names = []
   for i in range(3):
       name = input("Name: ")
       names.append(name)
   print(names)
   ```  

*Diese Beispiele fassen viele bisherige Themen zusammen.*

## Zusammenfassung / Spickzettel

**Wichtigste Python-Befehle und Konzepte:**

- `print(obj1, obj2, ...)` – Gibt Werte aus (mit Leerzeichen getrennt).  
- `input(prompt)` – Liest eine Eingabe als String (Anzeige von `prompt`).  
- Kommentare mit `#`, mehrzeilig mit `''' '''` oder `""" """`.  
- **Variablen**: Keine Typdeklaration nötig; Zuweisung per `=`. Variablen sind *case-sensitive*. Namen mit Buchstaben, Zahlen, `_` (nicht am Anfang). Beispiele: `myVar`, `_temp`, `value2`.  
- **Datentypen**: `int`, `float`, `str`, `bool`, `list`, `tuple`, `dict`, `set`.  
- **Listen**: Def. `list = [1,2,3]`. Wichtige Methoden:  
  - `append(e)`, `remove(e)`, `sort()`, `reverse()`, `count(e)`.  
  - Listen-Slicing: `list[start:stop]`.  
  - List Comprehension: `[x for x in liste if bedingung]`.  

- **Tupel**: Def. `tup = (1,2,3)`. Unveränderlich. Entpackung möglich `x,y,z = tup`.  
- **Dictionaries**: Def. `d = {"key": value}`. Zugriff `d["key"]`. Methoden: `keys()`, `values()`, `items()`, `get(key)`.  
- **Sets**: Def. `s = {1,2,3}` oder `set()`. Keine Duplikate. Methoden: `add()`, `remove()`, `union()`, `intersection()`, `difference()`.  

- **If/elif/else**:  
  ```python
  if bedingung1:
      ...
  elif bedingung2:
      ...
  else:
      ...
  ```  
  Vergleichsoperatoren: `==, !=, <, >, <=, >=`. `else` fängt alle anderen Fälle ab.  

- **Schleifen**:  
  - `for x in sequenz:` – iteriert über alle Elemente. Keine explizite Zählvariable notwendig.  
  - `while bedingung:` – wiederholt, solange Bedingung wahr ist.  
  - `break` – beendet Schleife sofort.  
  - `continue` – überspringt Rest der aktuellen Iteration.  
  - Optional `else`-Block nach Schleife (läuft, falls kein `break` ausgelöst wurde).

- **Funktionen**:  
  ```python
  def name(parameter1, parameter2):
      ...
      return wert  # optional
  ```  
  - Aufruf mit `name(arg1, arg2)`.  
  - `return` gibt einen Wert zurück. Ohne `return` gibt Funktion `None`.  
  - Beispiel aus [38]: `def my_function(): print("Hello")`.  
  - Funktionen vermeiden Code-Duplikate.  

- **Dateien**:  
  - `open("file", mode)`: Modus `"r"`, `"w"`, `"a"`, `"x"`, ggf. `"t"`/`"b"`.  
  - Mit `with open(...) as f:` wird automatisch geschlossen.  
  - `f.read()`, `f.readline()`, `f.readlines()`, `f.write(text)`.  

- **Fehlerbehandlung**:  
  ```python
  try:
      ...
  except SomeError:
      ...
  else:
      ...
  finally:
      ...
  ```  
  - `try` testet Code, `except` fängt Fehler ab.  
  - `finally` führt immer aus.  
  - Ohne `try` würde ein Laufzeitfehler das Programm beenden.  

- **Module/Import**: `import modulname` oder `from modul import name`. Ermöglicht Verwendung von Standard-Bibliotheken (z.B. `sys`, `math`, `datetime`, `os`, `shutil`).

- **Prüfungstipps**: Übe einfache Python-Aufgaben, teste Code in der Konsole. Achte auf richtige Einrückung und Typen (z.B. `str` vs. `int`). Viele Beispiele aus dem Unterricht (s.o.) wiederholen.

Dieser Spickzettel fasst alle Kernpunkte zusammen, die in den Übungen und im Unterricht durchgegangen wurden. Mit diesen Grundlagen kann man Python-Scripts schreiben, um Aufgaben zu automatisieren und zu lösen.

**Quellen:** W3Schools Python-Tutorials und Referenzen wurden konsultiert, u.a. zu List-, String- und File-Methoden. Weitere Beispiele basieren auf üblichen Python-Dokumentationen und den Unterrichtsinhalten.