Chunkformat Decodieren Klasse entsprechend Type wählen

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
dr_strom
User
Beiträge: 3
Registriert: Dienstag 30. Januar 2018, 08:35

Hallo zusammen,

ich bin in der Programmiersprache Python relativ neu, beherrsche aberbereits 1-2 andere (C und C++). Ich wollte mich für komplexere Aufgaben hier geistig etwas erweitern und habe daher angefangen Python zu lernen.

Hier habe ich jetzt eine kleine (aber für mich wichtige) "Übung".
Ich möchte ein Chunkformat decodieren und die darin enthaltenen Daten extrahieren und Informationen in einem Log festhalten.

Der Dateiaufbau ist ganz typisch:
1. 32-Bit Type (Integer LE)
2. 32-Bit Length (Integer LE)
3. Content (<length> Bytes)

Das ganze natürlich hierarisch, also es ist eine Art "List"-Chunktyp vorhanden. Ein Chunk könnte auf mal 4GiB groß sein. Auf parallele Auswertung muss ich vorerst nicht achten, das schaue ich mir später mal bei einem anderen Projekt an.

Ich habe mir das ganze wie folgt vorgestellt:
1. Eine Instanz der Klasse Reader liest aus der Datei 2x 4 Byte (Length und Type)
2. Type wird ausgewertet, für jeden möglichen wird gibt es eine Seperate Klasse die dessen Verarbeitung übernimmt (bekommt das Dateiobjekt übergeben um Daten selbst einlesen zu können) -> von der jeweiligen Klasse wird eine Instanz erstellt.

Alle Chunk-Typ-Klassen erben von einer Basisklasse Chunk.

Jetzt zu meiner Frage:
Aus C kenne ich das switch-case-Konstruk, welches es ja in Python3 nicht gibt. Ich könnte jetzt einfach ein sehr langes if-else bauen, welches alle möglichen Typen und die zugehörigen Klasseninstanzierungen enthält. Aber irgendwie finde ich das nicht schön. Etwa so (Code nicht ausformuliert, nur zur Anschauung):

Code: Alles auswählen

class Chunk:
	def __init__(self, f, length):
		#tue was für alle Chunks gleich ist
				                	
#Typ 0 ist der Headchunk
class ChunkTyp0(Chunk):
	def __init__(self, f, length):
		Chunk.__init__(self, f, length)
		while length > 0:
			itype = int.from_bytes(f.read(4), byteorder='little')
                	ilength = int.from_bytes(f.read(4), byteorder='little')
                	length -= ilength + 8
         	       if type == 0
      		    		obj = ChunkTyp0(f, length)
			elif type == 1:
				obj = ChunkTyp1(f, length)	
			elif type == 2:
				obj = ChunkTyp2(f, length)
			else:
				print('Unknown Chunktype')
				f.read(ilength)
		
class ChunkTyp1(Chunk):
	def __init__(self, f, length):
		Chunk.__init__(self, f, length)
		#Verarbeite den Chunk

class ChunkTyp2(Chunk):
	def __init__(self, f, length):
		Chunk.__init__(self, f, length)
		#Verarbeite den Chunk

class Reader:
	def __init__(self, f)
            while True:
                type = int.from_bytes(f.read(4), byteorder='little')
                length = int.from_bytes(f.read(4), byteorder='little')
                if length == '':
                    break
                if type == 0
          		obj = ChunkTyp0(f, length)
		elif type == 1:
			obj = ChunkTyp1(f, length)
		elif type == 2:
			obj = ChunkTyp2(f, length)
		else:
			print('Unknown Chunktype')
			break
			
if __name__ == '__main__':
	with open(argv[1], "rb") as f:
		main_Reader = reader(f)
Wie würdet ihr dieses Problem lösen? Vielleicht kennt Ihr ja einen eleganteren Weg. Ich hatte an die Dictionarys gedacht, weiß aber derzeit nicht, wie das syntaktisch gehen könnte. Das die Typeauswahl doppelt ist stört mich irgendwie am meisten.
Viele Grüße und danke für euren Input
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@dr_storm: Deine Idee mit Dictionaries ist richtig.

Zum Code: Deine Einrückungen sind kaputt. Eingerückt wird generell mit 4 Leerzeichen pro Ebene (keine Tabs und auch nicht 3 Leerzeichen). Statt int_frombytes würde ich Dir struct.unpack empfehlen. Der Reader ist eigentlich keine Klasse, sondern eine Funktion `read_chunk`, der einfach einen Chunk zurückliefert. Diese Funktion kannst Du sowohl im Hauptprogramm als auch in den Chunks.__init__-Methoden aufrufen.

Code: Alles auswählen

import sys
import struct

CHUNK_TYPES = {}
def read_chunks(f, length=None):
    if length is None:
	    pos = f.tell()
		f.seek(0, 2)
		length = f.tell()
		f.seek(pos, 0)
	result = []
	while length > 0:
		type, chunk_length = struct.unpack('<II', f.read(8))
		result.append(CHUNK_TYPES[type](f, chunk_length))
		length -= chunk_length + 8
	return result

class Chunk:
    def __init__(self, f, length):
        #tue was für alle Chunks gleich ist
	    self.chunks = read_chunks(f, length)
                               
#Typ 0 ist der Headchunk
class ChunkTyp0(Chunk):
    def __init__(self, f, length):
        Chunk.__init__(self, f, length)
CHUNK_TYPES[0] = ChunkTyp0

class ChunkTyp1(Chunk):
    def __init__(self, f, length):
        Chunk.__init__(self, f, length)
        #Verarbeite den Chunk
CHUNK_TYPES[1] = ChunkTyp1

class ChunkTyp2(Chunk):
    def __init__(self, f, length):
        Chunk.__init__(self, f, length)
        #Verarbeite den Chunk
CHUNK_TYPES[2] = ChunkTyp2

def main():
    with open(sys.argv[1], "rb") as f:
        chunks = read_chunks(f)

if __name__ == '__main__':
    main()
dr_strom
User
Beiträge: 3
Registriert: Dienstag 30. Januar 2018, 08:35

Hallo Sirius,
vielen Dank. Das sieht schon viel strukturierter aus. Der doppelte Code ist weg, viel eleganter.

Das mein Code nicht funktioniert wusste ich. Ich hab Ihn direkt ins Forum getippt um zu zeigen was ich meine (mit Tab, mein vim macht bei py dateien aus einem Tab 4 Leerzeichen; trotzdem danke für den Hinweis).

Als C-Programmierer sind die Zeilen 27,33 und 39 für mich natürlich außerst ungewohnt, aber damit werde ich früher oder später klar kommen.

Die Lösung mit dem length=None gefällt mir gut. Aber wenn ich deinen Code richtig verstehe, wird read_chunks nun bei allen Chunks ausgeführt (im Konstruktor der Basisklasse Chunk), also auch bei solchen die nur Rohdaten und keine weiteren Chunks enthalten. Oder?

Ich würde daher die Zeile 21 löschen und zwischen Zeile 26 und 27 wieder einfügen. Richtig?
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

@dr_strom: wie Dein Dateiformat aussieht, weißt nur Du.
dr_strom
User
Beiträge: 3
Registriert: Dienstag 30. Januar 2018, 08:35

Wohl war, ich wollte nur sichergehen, dass ich deinen Code richtig verstanden habe.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Eine kleine Anmerkung: Wenn man mit Indexwerten auf ein Dictionary zugreift, dann kann man genau so gut direkt eine Liste anstelle des Dicts nehmen. Sofern die verschiedenen Typen also tatsächlich durch aufeinanderfolgende Integer dargestellt werden, dann würde ich dieTyp-Klassen einfach hintereinander in eine Liste stecken.
Antworten