Umgang mit Dateien#

Midjourney: Librarian, ref. Giuseppe Arcimboldo

We don’t have better algorithms. We just have more data.

— Peter Norvig

Folien/PDF#

In Programmen müssen regelmäßig Daten geladen und gespeichert werden. Dies geschieht in Computern in Dateien, welche in Verzeichnissen organisiert sind. In der letztem Abschnitt zu Paketen haben wir bereits einige Beispiele gesehen die mit Dateien arbeiten. Diese wollen wir nun detaillierter kennen lernen.

Dateien lesen#

Eine typische Aufgabe ist es Dateien zu laden. Hierfür bietet Python die open()-Funktion mit dem Kürzel r (read). Hierfür gibt man der open()-Funktion den Pfad der zu ladenden Datei an und auch den Datentyp der Datei also ob die Datei eine Textdatei ist t oder eine binäre Datei b ist.

Die open()-Funktion verwendet man meist im with-Konstrukt, welcher die Datei einer Variable zuordnet (fi) und die Datei nach Beendigung des Blocks auch automatisch schließt. Zum lesen des Inhalts der Datei nutzen wir die read() des Dateiobjektes.

with open("geometry/shapes/Line.py", "tr") as fi:
    dateinhalt=fi.read()
    print(f"Datentyp Datei {type(fi)}")
    print(f"Datentyp Variable {type(dateinhalt)}\n")
    print(dateinhalt)
Datentyp Datei <class '_io.TextIOWrapper'>
Datentyp Variable <class 'str'>

from geometry.points.ImmutablePoint import ImmutablePoint

class Line:
    def __init__(self, start: ImmutablePoint, end: ImmutablePoint):
        if not isinstance(start, ImmutablePoint):
            raise TypeError("start not of type ImmutablePoint")
        if not isinstance(end, ImmutablePoint):
            raise TypeError("end not of type ImmutablePoint")
        self.start = start
        self.end = end

    def length(self):
        return self.start.distance(self.end)

In gleicher Form kann man auch binäre Dateien einlesen. Dafür tauschen wir den Dateityp t mit binär aus b und laden die Datei. Wir sehen, dass jetzt der Datentyp der geladenen Dateinhalts zu byte wechselt. Printen wir den Dateinhalt sehen wir auch direkt die Sonderzeichen in der Datei wie \r und \n welche für den Zeilenumbruch stehen.

with open("geometry/shapes/Line.py", "br") as fi:
    dateinhalt=fi.read()
    print(f"Datentyp Datei {type(fi)}")
    print(f"Datentyp Variable {type(dateinhalt)}\n")
    print(dateinhalt)
Datentyp Datei <class '_io.BufferedReader'>
Datentyp Variable <class 'bytes'>

b'from geometry.points.ImmutablePoint import ImmutablePoint\n\nclass Line:\n    def __init__(self, start: ImmutablePoint, end: ImmutablePoint):\n        if not isinstance(start, ImmutablePoint):\n            raise TypeError("start not of type ImmutablePoint")\n        if not isinstance(end, ImmutablePoint):\n            raise TypeError("end not of type ImmutablePoint")\n        self.start = start\n        self.end = end\n\n    def length(self):\n        return self.start.distance(self.end)'

Dateien schreiben#

In gleicher Weise können wir mit der open()-Funktion auch neue Dateien erzeugen. Hierfür nutzen wir das Kürzel w (write). Auch hier werden Textdateien mit t und binäre Dateien mit b unterschieden. Zum Schreiben der Datei nutzen wir die write()-Methode des Dateiobjektes fo.

with open("meineDatei.txt", "tw") as fo:
    dateinhalt="Meine eigener Inhalt"
    fo.write(dateinhalt)

Zum überprüfen lesen wir die Datei wieder.

with open("meineDatei.txt", "tr") as fi:
    print(fi.read())
Meine eigener Inhalt

Wichtig zu wissen ist, dass die Datei vollständig überschrieben wird.

with open("meineDatei.txt", "tw") as fo:
    dateinhalt="Neuer Inhalt"
    fo.write(dateinhalt)
with open("meineDatei.txt", "tr") as fi:
    print(fi.read())
Neuer Inhalt

Datei existenz testen#

Häufig will man testen ob eine Datei bereits existiert und dementsprechend diese laden oder z.B. neu erzeugen. Solche und andere Funktionen bietet die Standardbibliothek os die wir bereits kennen gelernt haben.

import os

if os.path.exists("meineDatei.txt"):
    print("Datei existiert")
else:
    print("Datei existiert noch nicht")
Datei existiert

Dateien auflisten#

Zum Auflisten aller Dateien in einem Verzeichnis folder können wir die Funktion os.listdir() benutzen. Mit der Funktion os.path.isfile() können wir prüfen ob der Name auf eine Datei oder ein Verzeichnis ist. Ist es eine Datei, so können wir diese mit der open-Funktion öffnen, um z.B. alle Dateien zu laden und die Anzahl der Code-Zeilen zu berechnen. Dafür nutzen wir anstatt der read-Funktion die readlines-Funktion um alle Zeilen einzeln in einer Liste zu erhalten.

import os

folder = "geometry/shapes/"
files = 0
codelines = 0
for count, name in enumerate(os.listdir(folder)):
	if os.path.isfile(os.path.join(folder, name)):
		with open(os.path.join(folder, name), "tr") as fi:
			codelines += len(fi.readlines())
			files += 1

print(f"{codelines} Codezeilen in {files} Dateien")
64 Codezeilen in 5 Dateien

Dateien löschen#

Die os-Paket bietet auch Funktionen, um Dateien zu löschen. Selbstverständlich sollte die mit Vorsicht verwendet werdet werden.

os.remove("meineDatei.txt")

Typische textuelle Dateiformate#

TXT-Dateien#

Eine der einfachsten Formate um Texte auf dem Computer zu Speichern sind Text-Dateien. Sie haben meist die Dateiendung .txt. Diese Dateiänderung haben wir bereits oben genutzt.

JSON-Dateien#

Heutzutage werden strukturierte Informationen oft im JSON-Format ausgetauscht. Insbesondere viele APIs von Webserver im Internet nutzen diesen Standard. Er hat den Vorteil, dass die Daten durch den Menschen lesbar bleiben und somit auch vom Webdeveloper interpretiert werden können. Im Kern ähnelt der Standard der Darstellung von dict in Python.

Wir können zum Beispiel den Datensatz zu einer Person in dem folgendem dict speichern.

person={
	"firstName": "John",
	"lastName": "Smith",
	"isAlive": True,
	"age": 25,
	"address": {
		"streetAddress": "21 2nd Street",
		"city": "New York",
		"state": "NY",
		"postalCode": "10021-3100" 
	},
 	"children": [],
	"spouse": None
}

Mit Hilfe des json-Pakets lässt sich dieser Datensatz jetzt einfach in ein JSON String umwandeln und in eine Datei schreiben.

import json

with open("person.json", "tw") as fo:
    json.dump(person, fo, indent=2)

Schauen wir uns einmal die Datei an. Da es eine textuelle Datei ist, können wir sie mit open(name, "tr") laden.

with open("person.json", "tr") as fi:
    dateinhalt=fi.read()
    print(dateinhalt)
{
  "firstName": "John",
  "lastName": "Smith",
  "isAlive": true,
  "age": 25,
  "address": {
    "streetAddress": "21 2nd Street",
    "city": "New York",
    "state": "NY",
    "postalCode": "10021-3100"
  },
  "children": [],
  "spouse": null
}

Wir sehen, dass die JSON-Representation dem oben definiertem Dictionary person sehr ähnlich ist. Die einzigen unterschiede sind, dass das in Pyton Großgeschriebene True hier klein geschrieben wird und das none aus Python mit null ersetzt wurde. Die Struktur beider Repräsentationen ist jedoch identisch.

Aus dieser Textdatei können wir jetzt unseren Datensatz direkt als dict wieder laden. Zur Darstellung nutzen wir diesmal pretty Print, weil es besser zu lesen ist.

from pprint import pprint

with open("person.json", "tr") as fi:
    person_geladen=json.load(fi)
    print(f"Datentyp {type(person_geladen)}")
    pprint(person_geladen)
Datentyp <class 'dict'>
{'address': {'city': 'New York',
             'postalCode': '10021-3100',
             'state': 'NY',
             'streetAddress': '21 2nd Street'},
 'age': 25,
 'children': [],
 'firstName': 'John',
 'isAlive': True,
 'lastName': 'Smith',
 'spouse': None}

Das geladene dict entspricht unserem ursprünglichem Dictionary person. Es hat sich zwar die Reihenfolge der Einträge geändert, aber dies ist nicht garantiert in J

GeoJSON-Dateien#

Eine besondere Variante des JSON-Formates das insbesondere für die Umweltinformatik relevant ist, ist das standartisierte GeoJSON-Format. Dieses auf JSON-basierende Format definiert wie bestimmte Geometrische Objekte wie Punkt, Linien und Polygone in JSON dargestellt werden sollen. Jedes Element wird dabei als JSON-Objekt (dict in Python) beschrieben und definiert die Attribute type und coordinates.

Ein Punkt ist dabei definiert als Element mit dem Typen Point und zwei Koordinaten.

punkt = {
    "type": "Point",
    "coordinates": [12.095843457646907, 54.075229197333016]
}

Eine Linie ist gegeben als LineString mit einer Liste an Punkt-Koordinaten, welche meist die Anfangs- und End-Koordinaten sind. Werden im LineString mehr als zwei Koordinaten angegeben haben wir eine Linienkette.

linie_oki_auf = {
    "type": "LineString",
    "coordinates": [
        [ 12.095844241344963, 54.075206445655795 ],
        [ 12.09606074723871, 54.075028604743636 ],
        [ 12.09593084370266, 54.074930156768204 ],
        [ 12.096282665780166, 54.07495873846247 ],
        [ 12.096558710795335, 54.07507941651065 ],
        [ 12.096840168457192, 54.074863466071434 ],
        [ 12.098052601464076, 54.07534617726671 ],
        [ 12.098187917647891, 54.07534617726671 ],
        [ 12.098317821183883, 54.07541286718799 ],
        [ 12.098377360305278, 54.075339825840246 ],
        [ 12.098501851194726, 54.0753779343855 ]
    ]
}

Ein Polygon ist definiert mit dem Typ Polygon wessen Koordinaten geben ist als Liste einer oder meherer Linienketten, welche geschlossen sind, so dass der Endpunkt mit dem Startpunkt übereinstimmt.

campus = { 
    "type": "Polygon",
    "coordinates": [
        [
            [ 12.093402064538196, 54.07479416035679 ],
            [ 12.094194380118807, 54.074246433609375 ],
            [ 12.094578770845374, 54.074103747303894 ] ,
            [ 12.095018074534778, 54.074191200259065 ],
            [ 12.095661340649713, 54.074435147002276 ],
            [ 12.096328140890677, 54.073947252082434 ],
            [ 12.098359920447564, 54.075010487417984 ],
            [ 12.098822758261605, 54.07471591412107 ],
            [ 12.099866104521425, 54.07523141601854 ],
            [ 12.09959938442529, 54.075383303749476 ],
            [ 12.100462302384159, 54.075700885391115 ],
            [ 12.098869826513692, 54.0770356222489 ],
            [ 12.09752838132394, 54.076602988106 ],
            [ 12.095394620552042, 54.076082900668524 ],
            [ 12.09422575895411, 54.07581595060367 ],
            [ 12.094743509729398, 54.07538790639916 ],
            [ 12.093402064538196, 54.07479416035679 ]
        ]
    ]
}

Da viele Objekte ja nicht neben der Geometrie auch noch weitere Attribute haben, gibt es das Hilfsobjekt Feature indem es das Attribute properties gibt in dem man eigenen Meta-Daten definieren kann. So können wir uns ein GeoJSON-Objekt definieren, um die Position des OKI zu speichern.

oki = {
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [12.095843457646907, 54.075229197333016]
  },
  "properties": {
    "name": "OKI",
    "addresse": "Justus-von-Liebig-Weg 2",
    "stadt": "Rostock",
    "postleitzahl": "18059",
    "land": "Deutschland"
  }
}

auf = {
  "type": "Feature",
  "geometry": {
    "type": "Point",
    "coordinates": [12.098494794410726, 54.075390284810425]
  },
  "properties": {
    "name": "AUF",
    "addresse": "Justus-von-Liebig-Weg 6",
    "stadt": "Rostock",
    "postleitzahl":"18059",
    "land": "Deutschland"
  }
}

weg_oki_auf = {
  "type": "Feature",
  "geometry": linie_oki_auf,
  "properties": {
    "name": "Weg OKI zu AUF",
    "stadt": "Rostock",
    "postleitzahl":"18059",
    "land": "Deutschland"
  }
}
    
campus_auf = {
  "type": "Feature",
  "geometry": campus,
  "properties": {
    "name": "Campus",
    "stadt": "Rostock",
    "postleitzahl":"18059",
    "land": "Deutschland"
  }
}

Eine Sammlung an Features wird in einer FeatureCollection abgelegt. Sie besitzt neben dem type die Liste features.

features = {
  "type": "FeatureCollection",
  "features": [
    oki,
    auf,
    weg_oki_auf,
    campus_auf
  ]
}

Zur Verarbeitung dieser GeoJSON-Objekte in Python können wir das Paket geojson nutzen. Wir installieren diese wieder mit pip.

pip install geojson  --quiet
/Users/jplonnigs/.zshenv:.:1: no such file or directory: /Users/jplonnigs/.cargo/env
/Users/jplonnigs/Documents/code/Lehre/ProgrammierUebung/.venv/bin/python: No module named pip
Note: you may need to restart the kernel to use updated packages.

Das Paket geojson bietet uns auch Standardklassen für Punkte, Linien und Polygone an, die wir uns zuvor selbst als Klassen definiert hatten. Aufgrund des umfangreichen Angebots and Paketen für Python lassen sich häufig auch Pakete finden, die entsprechende Klassen für die eigenen Problemstellungen bieten, so dass sich immer eine Suche lohnt. Ein Punkt in GeoJSON erzeugen wir mit dem Paket durch

from geojson import Point

geojson_punkt=Point((12.095843457646907, 54.075229197333016))
print(type(geojson_punkt))
print(geojson_punkt)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[20], line 1
----> 1 from geojson import Point
      3 geojson_punkt=Point((12.095843457646907, 54.075229197333016))
      4 print(type(geojson_punkt))

ModuleNotFoundError: No module named 'geojson'

Neue Instanzen lassen sich auch direkt aus dem JSON erzeugen. Hierfür benutzen wir die loads-Funktion des Pakets. Sie wandelt einen JSON-String in ein Objekt um. Um den JSON-String zu erzeugen, wandeln wir das Dictionary punkt in ein String um mit der Funktion json.dumps().

import geojson
import json

json_str=json.dumps(punkt)
gson_punkt=geojson.loads(json_str)

print(type(gson_punkt))
print(gson_punkt)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[21], line 1
----> 1 import geojson
      2 import json
      4 json_str=json.dumps(punkt)

ModuleNotFoundError: No module named 'geojson'

So lässt sich auch unser ganze Feature Collection laden.

import geojson
import json

json_str=json.dumps(features)
gson_features=geojson.loads(json_str)

print(type(gson_features))
print(gson_features)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[22], line 1
----> 1 import geojson
      2 import json
      4 json_str=json.dumps(features)

ModuleNotFoundError: No module named 'geojson'

Der Vorteil von GeoJSON-Objekten ist, dass wiederum viele weitere Pakete existieren, um dieses Format zu analysieren. Wollen wir zum Beispile unsere Feature Collection auf einer Karte visualisieren, können wir das Paket geojsonio nutzen.

pip install geojsonio  --quiet
/Users/jplonnigs/.zshenv:.:1: no such file or directory: /Users/jplonnigs/.cargo/env
/Users/jplonnigs/Documents/code/Lehre/ProgrammierUebung/.venv/bin/python: No module named pip
Note: you may need to restart the kernel to use updated packages.
import geojsonio

geojsonio.display(json_str)

Hide code cell output

---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[24], line 3
      1 import geojsonio
----> 3 geojsonio.display(json_str)

NameError: name 'json_str' is not defined

Wenn wir dem Link folgen kommen wir zu einer Webseite die uns die Polygone, Linie und Punkte anzeigt.

Campus

Weitere Anwendungen von GeoJSON werden wir in der Übung kennen lernen.

XML#

XML ist ein anderes sehr weit verbreitetes Dateiformat. Alle Webseiten im Internet nutzen z.B. dieses Format. Es ist älter als JSON und immer noch sehr beliebt, weil es erlaubt Schemata (XLS) zu definieren, gegen diese die Datei validiert werden kann. So kann man z.B. sicher stellen das HTML-Dateien korrekt sind.

Mit Hilfe der externen Pakete dicttoxml und xmltodict können XML-Dateien auch einfach geschrieben und gelesen werden. Wir installieren sie mit pip.

pip install dicttoxml xmltodict  --quiet
/Users/jplonnigs/.zshenv:.:1: no such file or directory: /Users/jplonnigs/.cargo/env
/Users/jplonnigs/Documents/code/Lehre/ProgrammierUebung/.venv/bin/python: No module named pip
Note: you may need to restart the kernel to use updated packages.
with open("person.xml", "tr") as fi:
    dateinhalt = fi.read()
    print(dateinhalt)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[27], line 1
----> 1 with open("person.xml", "tr") as fi:
      2     dateinhalt = fi.read()
      3     print(dateinhalt)

File ~/Documents/code/Lehre/ProgrammierUebung/.venv/lib/python3.12/site-packages/IPython/core/interactiveshell.py:343, in _modified_open(file, *args, **kwargs)
    336 if file in {0, 1, 2}:
    337     raise ValueError(
    338         f"IPython won't let you open fd={file} by default "
    339         "as it is likely to crash IPython. If you know what you are doing, "
    340         "you can use builtins' open."
    341     )
--> 343 return io_open(file, *args, **kwargs)

FileNotFoundError: [Errno 2] No such file or directory: 'person.xml'
import xmltodict

with open("person.xml", "tr") as fi:
    person_geladen = xmltodict.parse(fi.read(), xml_attribs=False)
    print(f"Datentyp {type(person_geladen)}")
    pprint(person_geladen)
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[28], line 1
----> 1 import xmltodict
      3 with open("person.xml", "tr") as fi:
      4     person_geladen = xmltodict.parse(fi.read(), xml_attribs=False)

ModuleNotFoundError: No module named 'xmltodict'

Auch hier entspricht das geladene dict unserem ursprünglichem.

CSV-Dateien#

Tabellen und Messwerte werden häufig als CSV-Dateien ausgetauscht. Dies ist ein sehr einfaches Format bei dem in der ersten Zeile der Text-Datei die Spaltennamen stehen und dann in jeder Zeile steht eine Reihe der Tabelle. Alle Werte werden durch Kommata , getrennt. Da das Komma im Deutschen als Dezimaltrennzeichen genutzt wird, wird hier häufig ein ; oder Tabulator \t genutzt.

Zum Verarbeiten von Tabellen nutzt man in Python meist die pandas-Bibliothek. Wollen wir zum Beispiel den Datensatz zweier Personen speichern, so wandeln wir diesen zuerst in eine pandas-Tabelle (DataFrame) um.

leute=[
    {"FirstName":"John", "LastName":"Smith", "IsAlive":True, "Age":25},
    {"FirstName":"Mary", "LastName":"Sue", "IsAlive":True, "Age":30}
]
import pandas as pd

tabelle=pd.DataFrame(leute)
tabelle
FirstName LastName IsAlive Age
0 John Smith True 25
1 Mary Sue True 30

Diesen können wir jetzt als CSV-Datei speichern.

tabelle.to_csv("leute.csv", index=False) # index=False sorgt dafür dass die Zeilennummern 0 und 1 weggelassen werden

Wir lesen probeweise die Datei wieder ein. Da sie text-basiert ist können wir open() mit tr nutzen.

with open("leute.csv", "tr") as fi:
    dateinhalt = fi.read()
    print(dateinhalt)
FirstName,LastName,IsAlive,Age
John,Smith,True,25
Mary,Sue,True,30

Wir können jetzt die CSV-Datei wieder in eine Tabelle laden.

tabelle_gelesen = pd.read_csv("leute.csv")
tabelle_gelesen
FirstName LastName IsAlive Age
0 John Smith True 25
1 Mary Sue True 30

und zu dem Dictionary zurückverwandeln

tabelle_gelesen.to_dict("records")
[{'FirstName': 'John', 'LastName': 'Smith', 'IsAlive': True, 'Age': 25},
 {'FirstName': 'Mary', 'LastName': 'Sue', 'IsAlive': True, 'Age': 30}]

Typische binäre Dateiformate#

XLS-Dateien#

Diese CSV-Dateien können wir auch einfach in andere Programme wie Microsoft Excel einlesen oder von dort aus speichern. Das Hausformat von Excel sind .xlsx Dateien. Diese können wir mit dem Paket openpyxl auch direkt aus pandas heraus schreiben. Wir installieren uns openpyxl mit ´pip`.

pip install openpyxl  --quiet
/Users/jplonnigs/.zshenv:.:1: no such file or directory: /Users/jplonnigs/.cargo/env
/Users/jplonnigs/Documents/code/Lehre/ProgrammierUebung/.venv/bin/python: No module named pip
Note: you may need to restart the kernel to use updated packages.

Nach der installation können wir einfach die Tabelle als Excel-Datei exportieren.

tabelle.to_excel("leute.xlsx", index=False) # index=False sorgt dafür dass die Zeilennummer weggelassen wird
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[37], line 1
----> 1 tabelle.to_excel("leute.xlsx", index=False) # index=False sorgt dafür dass die Zeilennummer weggelassen wird

File ~/Documents/code/Lehre/ProgrammierUebung/.venv/lib/python3.12/site-packages/pandas/util/_decorators.py:333, in deprecate_nonkeyword_arguments.<locals>.decorate.<locals>.wrapper(*args, **kwargs)
    327 if len(args) > num_allow_args:
    328     warnings.warn(
    329         msg.format(arguments=_format_argument_list(allow_args)),
    330         FutureWarning,
    331         stacklevel=find_stack_level(),
    332     )
--> 333 return func(*args, **kwargs)

File ~/Documents/code/Lehre/ProgrammierUebung/.venv/lib/python3.12/site-packages/pandas/core/generic.py:2436, in NDFrame.to_excel(self, excel_writer, sheet_name, na_rep, float_format, columns, header, index, index_label, startrow, startcol, engine, merge_cells, inf_rep, freeze_panes, storage_options, engine_kwargs)
   2423 from pandas.io.formats.excel import ExcelFormatter
   2425 formatter = ExcelFormatter(
   2426     df,
   2427     na_rep=na_rep,
   (...)   2434     inf_rep=inf_rep,
   2435 )
-> 2436 formatter.write(
   2437     excel_writer,
   2438     sheet_name=sheet_name,
   2439     startrow=startrow,
   2440     startcol=startcol,
   2441     freeze_panes=freeze_panes,
   2442     engine=engine,
   2443     storage_options=storage_options,
   2444     engine_kwargs=engine_kwargs,
   2445 )

File ~/Documents/code/Lehre/ProgrammierUebung/.venv/lib/python3.12/site-packages/pandas/io/formats/excel.py:943, in ExcelFormatter.write(self, writer, sheet_name, startrow, startcol, freeze_panes, engine, storage_options, engine_kwargs)
    941     need_save = False
    942 else:
--> 943     writer = ExcelWriter(
    944         writer,
    945         engine=engine,
    946         storage_options=storage_options,
    947         engine_kwargs=engine_kwargs,
    948     )
    949     need_save = True
    951 try:

File ~/Documents/code/Lehre/ProgrammierUebung/.venv/lib/python3.12/site-packages/pandas/io/excel/_openpyxl.py:57, in OpenpyxlWriter.__init__(self, path, engine, date_format, datetime_format, mode, storage_options, if_sheet_exists, engine_kwargs, **kwargs)
     44 def __init__(
     45     self,
     46     path: FilePath | WriteExcelBuffer | ExcelWriter,
   (...)     55 ) -> None:
     56     # Use the openpyxl module as the Excel writer.
---> 57     from openpyxl.workbook import Workbook
     59     engine_kwargs = combine_kwargs(engine_kwargs, kwargs)
     61     super().__init__(
     62         path,
     63         mode=mode,
   (...)     66         engine_kwargs=engine_kwargs,
     67     )

ModuleNotFoundError: No module named 'openpyxl'

Diese Datei ist erstmal eine binäre Datei. Wir können sie also nicht mit open() und tr lesen, sondern müssen die binäre Variante mit br nehmen.

with open("leute.xlsx", "br") as fi:
    dateinhalt = fi.read()
    print(dateinhalt)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[38], line 1
----> 1 with open("leute.xlsx", "br") as fi:
      2     dateinhalt = fi.read()
      3     print(dateinhalt)

File ~/Documents/code/Lehre/ProgrammierUebung/.venv/lib/python3.12/site-packages/IPython/core/interactiveshell.py:343, in _modified_open(file, *args, **kwargs)
    336 if file in {0, 1, 2}:
    337     raise ValueError(
    338         f"IPython won't let you open fd={file} by default "
    339         "as it is likely to crash IPython. If you know what you are doing, "
    340         "you can use builtins' open."
    341     )
--> 343 return io_open(file, *args, **kwargs)

FileNotFoundError: [Errno 2] No such file or directory: 'leute.xlsx'

Was wir sehen sind viele unverständliche binäre Zeichen. Dahinter steckt in diesem Fall eine komprimierte ZIP-Datei, da das .xlsx-Dateiformat eigentlich nur eine ZIP-Datei ist die mehrere XML-Dateien enthält.

ZIP-Dateien#

ZIP-Dateien sind Dateien, welche andere Dateien und Verzeichnisse enthalten und diese komprimieren. Dadurch können mehrere Dateien in einer einzigen zusammengefasst werden und verbrauchen weniger Speicher. Deshalb werden ZIP-Dateien gerne im Versand mehrere Dateien verwendet.

Auch die .xlsx Datei von Excel ist eine verkappte ZIP-Datei, die mehrere XML-Dateien in dem offenen Office-Format enthält.

Dies lässt sich zeigen in dem wir die Datei in eine ZIP-Datei umbenennen mit os.rename().

os.rename("leute.xlsx", "leute.zip")
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[39], line 1
----> 1 os.rename("leute.xlsx", "leute.zip")

FileNotFoundError: [Errno 2] No such file or directory: 'leute.xlsx' -> 'leute.zip'

Wollen wir die Dateien in der Zip-Datei sehen, so können wir diese mit dem ZipFile-Objekt des Standardpaktes zipfile öffnen. Es funktioniert genauso wie open() nur für ZIP-Dateien. Mit der Methode namelist können wir alle Dateien in der Zip-Datei auflisten.

import zipfile

with zipfile.ZipFile("leute.zip",'r') as zipdatei:
    for fname in zipdatei.namelist():
        print(fname)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[40], line 3
      1 import zipfile
----> 3 with zipfile.ZipFile("leute.zip",'r') as zipdatei:
      4     for fname in zipdatei.namelist():
      5         print(fname)

File ~/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/zipfile/__init__.py:1336, in ZipFile.__init__(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps, metadata_encoding)
   1334 while True:
   1335     try:
-> 1336         self.fp = io.open(file, filemode)
   1337     except OSError:
   1338         if filemode in modeDict:

FileNotFoundError: [Errno 2] No such file or directory: 'leute.zip'

Um aus der Zip-Datei eine einzelne Datei zu lesen, können wir die read()-Methode nutzen. Laden wir z.B. die ‘xl/worksheets/sheet1.xml’ welche unsere Daten enthält so sehen wir unsere Daten in der typischen XML-Struktur.

import zipfile
from pprint import pprint

with zipfile.ZipFile("leute.zip",'r') as zipdatei:
    xmldatei = zipdatei.read("xl/worksheets/sheet1.xml")
    print(xmldatei)
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[41], line 4
      1 import zipfile
      2 from pprint import pprint
----> 4 with zipfile.ZipFile("leute.zip",'r') as zipdatei:
      5     xmldatei = zipdatei.read("xl/worksheets/sheet1.xml")
      6     print(xmldatei)

File ~/.local/share/uv/python/cpython-3.12.10-macos-aarch64-none/lib/python3.12/zipfile/__init__.py:1336, in ZipFile.__init__(self, file, mode, compression, allowZip64, compresslevel, strict_timestamps, metadata_encoding)
   1334 while True:
   1335     try:
-> 1336         self.fp = io.open(file, filemode)
   1337     except OSError:
   1338         if filemode in modeDict:

FileNotFoundError: [Errno 2] No such file or directory: 'leute.zip'

Mit der parse-Funktion des xmltodict-Paketes können wir diese XML-Datei zum Beispiel in ein ´dict´ in Python umwandeln.

xmldict = xmltodict.parse(xmldatei, xml_attribs=False)
pprint(xmldict)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[42], line 1
----> 1 xmldict = xmltodict.parse(xmldatei, xml_attribs=False)
      2 pprint(xmldict)

NameError: name 'xmltodict' is not defined

Unser ursprüngliches Dictionary leute, welches wir oben definiert haben ist in diesem Dictionary nicht mehr erkenntlich. Das liegt daran, dass dieses Format von Microsoft Excel definiert wurde und nicht speziel für unseren Zweck ausgelegt ist. Wichtig ist allerdings, dass das Format durchaus durch Menschen lesbar ist, so dass heutzutage viele andere Tools, wie LibreOffice, Google Docs, etc. dieses Format lesen und schreiben können. Das ist ein wichtiger Grund für die Nutzung von offenen XML-Formaten.

Quiz#

--- shuffleQuestions: true shuffleAnswers: true --- ### Warum werden in Programmen häufig Dateien verwendet? - [x] Um Daten dauerhaft zu speichern - [ ] Um den Code schneller auszuführen - [ ] Um Variablen automatisch zu initialisieren - [ ] Um grafische Oberflächen zu erstellen ### Wo befinden sich Dateien in einem Computer? - [x] In Verzeichnissen (Ordnern) - [ ] Im RAM - [ ] In der CPU - [ ] Nur in der Cloud ### Welche der folgenden Aussagen zum Umgang mit Dateien in Python ist korrekt? - [x] Man sollte Dateien nach dem Öffnen immer schließen oder `with` verwenden. - [ ] Dateien können ohne Pfadangabe nur aus dem Internet geladen werden. - [ ] Es gibt keine Möglichkeit, Dateien zeilenweise zu lesen. - [ ] Python unterstützt keinen Schreibmodus. ### Was macht der folgende Code? ```python with open("notizen.txt", "r") as f: zeilen = f.readlines() ``` - [x] Er liest alle Zeilen der Datei `notizen.txt` in eine Liste ein. - [ ] Er schreibt alle Zeilen in die Datei. - [ ] Er löscht die Datei. - [ ] Er gibt jede Zeile sofort auf dem Bildschirm aus. ### Was bewirkt die Funktion `open("datei.txt", "r")` in Python? - [x] Sie öffnet die Datei `datei.txt` zum Lesen. - [ ] Sie erstellt eine neue Datei mit dem Namen `datei.txt`. - [ ] Sie öffnet die Datei `datei.txt` zum Schreiben. - [ ] Sie löscht den Inhalt der Datei `datei.txt`. ### Was passiert, wenn die Datei nicht existiert und `open("nichtda.txt", "r")` ausgeführt wird? - [x] Es wird eine Fehlermeldung `FileNotFoundError` ausgelöst. - [ ] Eine leere Datei wird erstellt. - [ ] Das Programm läuft normal weiter. - [ ] Die Datei wird automatisch im Schreibmodus geöffnet. ### Welcher Fehler steckt in diesem Beispiel? ```python file = open("daten.txt", "r" data = file.read() ``` - [x] Eine schließende Klammer fehlt in der `open`-Funktion. - [ ] Der Dateiname ist ungültig. - [ ] `read()` kann nur auf binäre Dateien angewendet werden. - [ ] `open` ist kein gültiger Befehl. ### Welcher Modus öffnet eine Datei zum Schreiben und überschreibt den Inhalt? - [x] "w" - [ ] "r" - [ ] "a" - [ ] "x" ### Was passiert bei folgendem Code, wenn die Datei bereits existiert? ```python with open("ausgabe.txt", "w") as f: f.write("Hallo Welt") ``` - [x] Der alte Inhalt der Datei wird gelöscht und überschrieben. - [ ] Es wird ein Fehler ausgelöst. - [ ] Der neue Text wird an das Ende der Datei angehängt. - [ ] Die Datei wird nicht verändert. ### Wie prüft man in Python, ob eine Datei existiert? - [x] Mit `os.path.exists("datei.txt")` - [ ] Mit `open("datei.txt")` - [ ] Mit `exists("datei.txt")` - [ ] Mit `os.exist("datei.txt")` ### Was ist notwendig, um `os.path.exists` nutzen zu können? - [x] Das Modul `os` muss importiert werden. - [ ] Das Modul `datetime` muss importiert werden. - [ ] Es braucht keine Imports. - [ ] `os.path.exists` ist eine eingebaute Funktion. ### Welche Funktion listet alle Dateien in einem Verzeichnis auf? - [x] `os.listdir()` - [ ] `os.showfiles()` - [ ] `os.files()` - [ ] `list.files()` ### Wie kann man mit Python nur `.txt`-Dateien aus einem Ordner auflisten? - [x] Mit einer Kombination aus `os.listdir()` und einer Filterung per Schleife - [ ] Nur mit `os.gettxtfiles()` - [ ] Automatisch mit `open("*.txt")` - [ ] Mit `file.list("*.txt")` ### Wie löscht man eine Datei in Python? - [x] Mit `os.remove("datei.txt")` - [ ] Mit `os.delete("datei.txt")` - [ ] Mit `os.clear("datei.txt")` - [ ] Mit `os.erase("datei.txt")` ### Wie lädt man eine JSON-Datei in Python? - [x] Mit `json.load(open("datei.json"))` - [ ] Mit `json.read("datei.json")` - [ ] Mit `json.load("datei.json")` - [ ] Mit `json.open("datei.json")` ### Was ist der Zweck von `json.dump()`? - [x] Um Python-Objekte in eine JSON-Datei zu schreiben. - [ ] Um JSON-Daten zu lesen. - [ ] Um JSON-Daten zu löschen. - [ ] Um JSON-Daten zu formatieren. ### Was ist JSON? - [x] Ein textbasiertes Format zum Austausch von Daten. - [ ] Ein binäres Dateiformat. - [ ] Ein Bildformat ### Was sind textbasierte Dateien? - [ ] *.png - [x] *.csv - [x] *.xml - [x] *.json - [ ] *.jpg ### Was ist der Unterschied zwischen JSON und XML? - [x] JSON ist einfacher und kompakter, XML ist umfangreicher und flexibler. - [ ] JSON ist älter als XML. - [ ] XML ist einfacher zu lesen als JSON. - [ ] JSON kann keine verschachtelten Strukturen darstellen.