OOP mit Dateien

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.
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

Hallo zusammen,

ich versuche mich wieder ein bisschen mehr mit OOP, also dem Python-Kern, zu beschäftigen, in der Hoffnung, das mein Code besser und anschaulicher wird.
Die klassischen Tutorials mit

Code: Alles auswählen

class Person:
    def _init_(self, name, age):
	self.name = name
	self.age = age
		
paul = Person('Paul', 25)

sind mir durchaus bekannt. Ich bekomme aber die direkte Anwendung für meine Projekte nicht so ganz umgesetzt.
Bei mir wird viel mit Dateien gemacht, die Daten enthalten und am Ende eigentlich immer in x,y-Koordinaten enden. Dabei aber verschiedene Aufbauten/Header/Formate(.txt, .pdf, .csv) enthalten.
Ich habe daher meistens im Code sowas wie

Code: Alles auswählen

def pdf_data_to_list(filename):
    with open(filename) as pdf:
        lines = pdf.readlines()
    first_line_to_use = lines.index("blub")
    last_line_to_use = lines.index("blub")
    eins, zwei = first_line_to_use.split("\t")
    
def csv_data_to_list(filename):
    with open(filename) as pdf:
        lines = pdf.readlines()
    eins, zwei = lines.split("\t")

def txt_to_list(filename):
    #do stuff
und so weiter, dass ich eben am Ende eine Liste mit den Koordinaten habe. Es ist aber an sich ja eher funktional und weniger übersichtlich, wenn dann noch plotting der Daten etc. dazu kommt.
Wie geht man sowas da genauer an? Wo finde ich dafür gute Lernbeispiele? Helfen mir Beispiele da überhaupt irgendwie? Da mein eigentlicher Fachbereich ein komplett anderer ist.

Danke und Grüße
Grüße
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

also bei OOP an sich gäbe es noch das Stich- (und Such-)Wort "Entwurfsmuster" bzw. "Entwurfsmuster in Python".

Bei den Tutorials finde ich die mit Autos jetzt besser als Peter, Paul & Mary. Bei Kraftfahrzeugen passen nämlich noch schön die Methoden rein, die sich z.B. PKW, LKW, Omnibus teilen.... bei Peter sehe ich jetzt nur Daten.

Die oben beschriebenen Funktionen verteilen sich bei mir gedanklich gleich auf folgende Klassen und Methoden:

Code: Alles auswählen

class Datei(object):
    def to_datei(self, dateiname):
        pass
        # hier passiert nur das was bei allen passiert

class Csv(Datei):
    def to_datei(self, dateiname):
        super().to_datei(dateiname)
        # nun kommt der csv-spezifische Kram

class Pdf(Datei):
    def to_datei(self, dateiname):
        super().to_datei(dateiname)
        # und alles für PDF
        
class Txt(Datei):
    def to_datei(self, dateiname):
        super().to_datei(dateiname)
        # und das Verhalten für Txt-Dateien
Gute aktuelle Tutorials kenne ich jetzt leider nicht, meine Literatur hier ist schon etwas in die Jahre gekommen. Nach dem ich das mit der objektorientierten Programmierung schon leidlich gut konnte (und UML), ist mir mal "Objektorientierte Modellierung mit UML: Das Fundament" in die Hände gefallen. Keine Programmiersprache, aber viel Grundlagen bzw. Theorie.

Mein Vorschlag:
ich versuche mich wieder ein bisschen mehr mit OOP, also dem Python-Kern, zu beschäftigen, in der Hoffnung, das mein Code besser und anschaulicher wird.
ignoriere erstmal das 'P' (ob das nun für Python oder Programmierung steht), suche dir mal was zur 'objektorientierten Modellierung' und fange damit an (das Thema 'Entwurfsmuster' gehört da auch zu). Das ist erst mal unabhängig von irgendwelchen Programmiersprachen. Danach geht es dann daran, wie man den ganzen Kram in Python umsetzt.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Dein Beispiel ist so überhaupt nicht für Klassen geeignet, eine Klasse besteht immer aus Daten, die mit Methoden angereichert sind. Also mindestens ein Datum und mindestens zwei Methoden, die damit arbeiten.
Du hast je eine Funktion und keine Daten.

Wenn Du ein bisschen mehr mit OOP machen möchtest, mußt Du Dir leider ein anderes Beispiel aussuchen,
Benutzeravatar
__blackjack__
User
Beiträge: 13063
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Karlirex: An dem Beispielcode ist ein bisschen unglücklich, dass der nicht funktioniert und man so nur erahnen kann was `eins` und `zwei` bedeuten soll, weil das zudem halt auch noch schlechte Namen sind. So kann man jetzt auch als aussenstehender keinen guten Namen für die Klasse finden, und damit auch schlechter einschätzen ob das überhaupt Sinn macht eine Klasse daraus zu machen nur weil die Dinge deren Bedeutung wir nicht kennen, die anscheinend immer als Paar auftreten, gleich benannt sind.

Sollten die Funktionen jeweils `eins` und `zwei` zurückgeben, dann könnte es Sinn machen die zu einem Objekt zusammenzufassen. Muss aber auch nicht sein. Kann man so halt nicht wirklich sagen.

Schritt eins ist in der Regel zu schauen wo man Werte hat, die zusammengehören, weil sie ein ”Ding” oder ein Konzept beschreiben und sich deshalb sinnvoll in einem Objekt zusammenfassen lassen. Das ist dann noch nicht OOP, sondern erst mal nur das wofür man auch in in Sprachen ohne spezielle OOP-Unterstützung Verbunddatentypen (TYPE, RECORD, struct, …) hat. Da hat Python nichts spezifischeres für, weil Klassen diese Aufgabe auch prima erfüllen können.

In dem so unvollständigen Beispiel könnte man sich beispielsweise vorstellen, eine Klasse zu haben, welche die allen Formaten gemeinsamen Headerinformationen enthalten und die Koordinaten daten. Statt das man diese Informationen als Einzelwerte beziehungsweise in anonymen Tupeln in der Gegend herum reicht.

Von den Stichworten „Entwurfsmuster“ und UML würde ich persönlich abraten. Da kommen mir bei Anfängern zu oft zu komische Sachen bei heraus. Entwurfsmuster werden zu oft als Selbstzweck missverstanden, nach dem Motto das Programm wird besser, je mehr Code man in irgendwelcher Muster gepresst hat — ob die da nun rein gehören oder nicht. Und oft werden die *Beispiel*muster auch zu wörtlich genommen, das alles genau so wie im *Beispiel* benannt und organisiert sein muss.

UML ist für statisch typisierte Programmiersprachen wo alles in Klassen und Methoden organisiert ist und hat so seine Probleme mit dynamischen Programmiersprachen und Konzepten die nicht OOP sind. Ist also für Multiparadigmen-Sprachen wie Python nur geeignet um einen Teil eines Programms sinnvoll zu beschreiben. Leute die auf UML setzen, benutzen dann meistens nur den Teil von Python und die Idiome, die sich in UML ausdrücken lassen, und verschenken Potential.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

Sirius3 hat geschrieben: Freitag 27. Januar 2023, 12:00 Dein Beispiel ist so überhaupt nicht für Klassen geeignet, eine Klasse besteht immer aus Daten, die mit Methoden angereichert sind. Also mindestens ein Datum und mindestens zwei Methoden, die damit arbeiten.
Du hast je eine Funktion und keine Daten.

Wenn Du ein bisschen mehr mit OOP machen möchtest, mußt Du Dir leider ein anderes Beispiel aussuchen,
Das Beispiel ist ja so gewählt, weil es an sich, mein reales Problem angeht. Dadurch, dass ich eigentlich nie Klassen oder Objekte verwende, sondern meist Dinge nur in Funktionen packe, weiß ich eben auch nicht ob das so sinnvoll ist. (Habe bisher am meisten mit Python Dinge gemacht, wie Flask oder normales Datenplotten, wo man da eigentlich drumherum kann bzw. bei Flask irgendwie auch muss).
__blackjack__ hat geschrieben: Freitag 27. Januar 2023, 13:30 @Karlirex: An dem Beispielcode ist ein bisschen unglücklich, dass der nicht funktioniert und man so nur erahnen kann was `eins` und `zwei` bedeuten soll, weil das zudem halt auch noch schlechte Namen sind. So kann man jetzt auch als aussenstehender keinen guten Namen für die Klasse finden, und damit auch schlechter einschätzen ob das überhaupt Sinn macht eine Klasse daraus zu machen nur weil die Dinge deren Bedeutung wir nicht kennen, die anscheinend immer als Paar auftreten, gleich benannt sind.

Sollten die Funktionen jeweils `eins` und `zwei` zurückgeben, dann könnte es Sinn machen die zu einem Objekt zusammenzufassen. Muss aber auch nicht sein. Kann man so halt nicht wirklich sagen.

Schritt eins ist in der Regel zu schauen wo man Werte hat, die zusammengehören, weil sie ein ”Ding” oder ein Konzept beschreiben und sich deshalb sinnvoll in einem Objekt zusammenfassen lassen. Das ist dann noch nicht OOP, sondern erst mal nur das wofür man auch in in Sprachen ohne spezielle OOP-Unterstützung Verbunddatentypen (TYPE, RECORD, struct, …) hat. Da hat Python nichts spezifischeres für, weil Klassen diese Aufgabe auch prima erfüllen können.

In dem so unvollständigen Beispiel könnte man sich beispielsweise vorstellen, eine Klasse zu haben, welche die allen Formaten gemeinsamen Headerinformationen enthalten und die Koordinaten daten. Statt das man diese Informationen als Einzelwerte beziehungsweise in anonymen Tupeln in der Gegend herum reicht.
Nunja es soll zunächst einmal Dateien geöffnet und gelesen werden, diese dann ggf. gekürzt dann weiter verwendet(Plot oder besagte gekürzte Speicherung). Ein Freund kam auf mich zu, schaute sich den Code an und meinte eben, dass sei "sehr funktional" und es gäbe wenig Klarheit . Dazu verwende ich aktuell Klassen eher zum Unterscheiden der Geräte. Ich solle mir doch vllt. besser überlegen etwas in Objekte zu packen, die das Gleiche tun um so vllt auch verschiedene Dateien besser unterschiedlich bearbeiten zu können.

Hier der lange konkrete Fall:

Code: Alles auswählen

class CopyFileEventHandler(LoggingEventHandler):
    def on_created(self, event):
        super().on_created(event)
        what = 'directory' if event.is_directory else 'file'
        from time import sleep
        sleep(0.2)
        if not event.is_directory:
            if "unprocessed_data" in os.path.dirname(event.src_path):
                try:
                    shutil.copy2(event.src_path,
                        f"{os.path.dirname(event.src_path).replace('unprocessed_data', 'processed_data')}\\generated_{os.path.basename(event.src_path)}")
                except PermissionError:
                    with open("debug.log", "a") as logfile:
                        logfile.write(f"Zugangsfehler: {os.path.dirname(event.src_path)}\\{os.path.basename(event.src_path)} \n")
            sleep(0.5)
            if "unprocessed_data" and "hplc" in event.src_path.lower():
                DeviceTwo.running_two(event.src_path)
            if "unprocessed_data" and "exported" in event.src_path.lower():
                DeviceOne.running_one(event.src_path)


class DeviceOne:
    def get_xy_data(filename):
        with open(filename) as device_one_text_file:
            full_data = device_one_text_file.readlines()
        angel_list = []
        intensity_list = []
        for line in full_data[1::]:
            angel, intensity = line.replace("\n", "").split(' ')
            angel_list.append(float(angel))
            ntensity_list.append(float(intensity))
        return angel_list, intensity_list

    def running_one(filename):
        if not "unprocessed_data" in os.path.dirname(filename):
            if not ".png" in os.path.basename(filename):
                if not ".xlsx" in os.path.basename(filename):
                    angel_list, intensity_list = DeviceOne.get_xy_data(filename)
                    generate_plot(filename, angel_list, intensity_list , "One")


class DeviceTwo:
    def get_xy_data_chromatogramm(filename):
        with open(filename) as device_two_text_file:
            full_data = device_two_text_file.readlines()
        if "Indentifier" in full_data[1]:
            RI_first_line_index = (full_data.index('[One]\n') + 8)
            RI_last_line_index = (full_data.index('[Second]\n') - 1)
            UV_first_line_index = (full_data.index('[First]\n') + 11)
            UV_last_line_index = (full_data.index('[Last)]\n') - 1)
            RI_lines = full_data[RI_first_line_index:RI_last_line_index]
            UV_lines = full_data[UV_first_line_index:UV_last_line_index]
            RI_time = []
            RI_intensity = []
            UV_time = []
            UV_intensity = []
            for line in RI_lines:
                time, intensity = line.replace('\n','').split('\t')
                RI_time.append(float(time.replace(',', '.')))
                RI_intensity.append(float(intensity))
            for line in UV_lines:
                time, intensity = line.replace('\n', '').split('\t')
                UV_time.append(float(time.replace(',', '.')))
                UV_intensity.append(float(intensity))
        else:
            RI_first_line_index = (full_data.index('[OtherOne]\n') + 8)
            RI_last_line_index = (full_data.index('[OtherSecond]\n') - 1)
            UV_first_line_index = (full_data.index('[OtherFirst]\n') + 11)
            UV_last_line_index = (full_data.index('[OtherLast]\n') - 1)
            RI_lines = full_data[RI_first_line_index:RI_last_line_index]
            UV_lines = full_data[UV_first_line_index:UV_last_line_index]
            RI_time = []
            RI_intensity = []
            UV_time = []
            UV_intensity = []
            for line in RI_lines:
                time, intensity = line.replace('\n', '').split('\t')
                RI_time.append(float(time))
                RI_intensity.append(float(intensity))
            for line in UV_lines:
                time, intensity = line.replace('\n', '').split('\t')
                UV_time.append(float(time))
                UV_intensity.append(float(intensity))
        return RI_time, RI_intensity, UV_time, UV_intensity

    def get_mass_data_chromatogramm(filename):
        with open(filename) as device_two_text_file:
            full_data = device_two_text_file.readlines()

        if "Indentifier" in full_data[1]:
            RI_mass_first_line_index = (full_data.index('[First]\n') + 3)
            RI_mass_last_line_index = (full_data.index('[Last]\n') - 1)
            RI_mass_lines = full_data[RI_mass_first_line_index:RI_mass_last_line_index]
            name_list = []
            area_list = []
            concentration_list = []
            for line in RI_mass_lines:
                Area, Name, Conc = line.split("\t")
                name_list.append(Name)
                area_list.append(Area)
                concentration_list.append(Conc)

        else:
            RI_mass_first_line_index = (full_data.index('[OtherFirst]\n') + 3)
            RI_mass_last_line_index = (full_data.index('[OtherLast]\n') - 1)
            RI_mass_lines = full_data[RI_mass_first_line_index:RI_mass_last_line_index]
            name_list = []
            area_list = []
            concentration_list = []
            for line in RI_mass_lines:
                Area, Conc, Name = line.split("\t")
                name_list.append(Name)
                area_list.append(Area)
                concentration_list.append(Conc)
        return name_list, area_list, concentration_list

    def generate_excel(filename, name_list, area_list, concentration_list):
        import pandas as pd
        df = pd.DataFrame({"Name: ": name_list, "Area: ": area_list, "Konz: ":concentration_list})
        df = df.T
        df.to_csv(f"{os.path.dirname(filename).replace('unprocessed_data', 'processed_data')}\\{os.path.basename(filename)}_short.txt")

    def running_two(filename):
        if not "unprocessed_data" in os.path.dirname(filename):
            if not ".png" in os.path.basename(filename):
                if not ".xlsx" in os.path.basename(filename):
                    if "short" in os.path.basename(filename):
                        return
                    else:
                        RI_time, RI_intensity, UV_time, UV_intensity = ShimadzuHPLC.get_xy_data_chromatogramm(filename)
                        generate_plot(filename, RI_time, RI_intensity, "RI")
                        generate_plot(filename, UV_time, UV_intensity, "UV")
                        name_list, area_list, concentration_list = ShimadzuHPLC.get_mass_data_chromatogramm(filename)
                        ShimadzuHPLC.generate_excel(filename, name_list, area_list, concentration_list)


def generate_plot(save_path, x_coordinate, y_coordinate, ending):
    plt.plot(x_coordinate, y_coordinate)
    plt.savefig(f"{os.path.dirname(save_path).replace('unprocessed_data', 'processed_data')}\\{os.path.basename(save_path)}_{ending}.png")
    plt.clf()

def pdf_file_to_txt(filename):
    import PyPDF2

    pages_text_list = []
    with open(filename, 'rb') as pdfFileObject:
        pdfReader = PyPDF2.PdfReader(pdfFileObject)
        for page in range(len(pdfReader.pages)):
            pageObject = pdfReader.pages[page]
            pages_text_list.append(pageObject.extract_text())
    return pages_text_list
    
###hiernach kommt "nur" die if main und der Start vom Watchdog mit Pathangabe
Theoretisch sollen noch andere Geräte dazu kommen, die bei mir in verschiedenen Dateitypen, eben PDF, CSV etc. ihre Daten speichern. Und ja wahrscheinlich geht das eben irgendwie deutlich schöner. Ob OOP da das richtige Stichwort war vom Freund, weiß ich aber eben auch nicht. Vielleicht fällt es mir auch einfach zu schwer, sauber zu coden abseits von den Tutorials, da daneben die Dinge doch irgendwie direkt groß und komplex werden.

Danke für eure Mithilfe, Grüße
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

__blackjack__ hat geschrieben: Freitag 27. Januar 2023, 13:30 Von den Stichworten „Entwurfsmuster“ und UML würde ich persönlich abraten. Da kommen mir bei Anfängern zu oft zu komische Sachen bei heraus. Entwurfsmuster werden zu oft als Selbstzweck missverstanden, nach dem Motto das Programm wird besser, je mehr Code man in irgendwelcher Muster gepresst hat — ob die da nun rein gehören oder nicht. Und oft werden die *Beispiel*muster auch zu wörtlich genommen, das alles genau so wie im *Beispiel* benannt und organisiert sein muss.
Dem stimme ich zu. :wink: Naja, dem ersten Satz halt teilweise. (UML hatte ich auch nie ernsthaft genutzt, aber so lautet der Titel vom Buch nun mal. )
"Anfänger": Anfänger beim Programmieren an sich / Anfänger bei der objektorientierten Programmierung / Anfänger beim Thema Entwurfsmuster oder welche?

Die obigen Warnungen sind definitiv sinnvoll. Die Fehler hatte ich damals, als ich bei den Entwurfsmuster Anfänger war, auch gemacht. Mehr oder weniger...
Aber es sind eben erprobte Möglichkeiten wie man mit dem ganzen Haufen an objektorientierten Konzepten (Klassen, Objekte, Instanzen, Vererbung, Aggregation, Metaklassen, usw...) umgehen kann um bestimmte Probleme zu lösen.

Und es wäre schön (stelle ich mir so vor) wenn man den Namen eines Musterns in den Raum werfen/zur Diskussion stellen könnte um dann darüber zu diskutieren ob das Muster beim aktuellen Problem nun hilfreich ist.
Seit vielleicht 15 Jahren bekomme ich Geld dafür mich mit einer Programmiersprache herum zu plagen in der es weder Klassen, noch Objekte gibt, sondern einfach nur Daten und Funktionen...

Daher denke ich über Entwurfsmuster nur noch nach wenn ich mich mal dazu aufraffe privat mal etwas nettes in Python zu machen.... und das hat in den letzten Jahren schon stark nach gelassen. Damit gibt es für mich auch meist keine Notwendigkeit über verschiedene Muster zu philosophieren... das beginnt erst jetzt wieder so langsam, nach dem ich mich hier (wieder) angemeldet hatte.
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

Karlirex hat geschrieben: Freitag 27. Januar 2023, 14:30 Und ja wahrscheinlich geht das eben irgendwie deutlich schöner. Ob OOP da das richtige Stichwort war vom Freund, weiß ich aber eben auch nicht.
OOP ist eben eine andere Möglichkeit die Probleme zu lösen als die imperative Lösung bei der alles in Funktionen gepackt wird.
Die Probleme die du jetzt bei deiner aktuellen Herangehensweise hast, die hast du beim OOP nicht......... dafür hast du dann ganz andere Probleme! :) Ob das nun mehr oder weniger sind, ob sie einfacher oder komplexer sind, ... das kann man so nicht sagen/beurteilen. :wink:
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

Karlirex hat geschrieben: Freitag 27. Januar 2023, 14:30 Ein Freund kam auf mich zu, schaute sich den Code an und meinte eben, dass sei "sehr funktional" und es gäbe wenig Klarheit . Dazu verwende ich aktuell Klassen eher zum Unterscheiden der Geräte. Ich solle mir doch vllt. besser überlegen etwas in Objekte zu packen, die das Gleiche tun um so vllt auch verschiedene Dateien besser unterschiedlich bearbeiten zu können.
Mmh, eine Bedeutung von "funktional" ist doch "es führt die Funktion aus für die es gedacht/gemacht ist". :) Aber wenn ich mir den langen Code so anschaue, dann vermute ich mal dass der Freund es doch etwas anders meinte. :wink: ... und ich schließe mich dem Freund voll und ganz an! Viele Funktionen definiert und eher keine wirklichen Klassen und Objekte... :roll: funktioniert der Code überhaupt? Der macht das was er soll? :roll: Habe ich so ja noch nie gesehen... :shock:

Zu den verstreuten Imports schreibe ich jetzt nichts, da kommen sicherlich noch von anderen Seiten Kommentare. :)

Das unterscheiden der Geräte durch unterschiedlichen Klassen ist ja erst mal nicht verkehrtes. Bei meinem letzten objektorientierten Projekt (damals in Delphi bzw. ObjectPascal) hatten wir so Klassen sinngemäß "(an)steuerbares Messgerät", "steuerbare Last", "Prüfling",...

"Ansteuerbar" ist hier vielleicht nicht der richtige Begriff, es waren alles Geräte die irgendwie (u.a. per https://de.wikipedia.org/wiki/IEC-625-Bus) mit dem PC vom Messplatz verbunden waren und vom Programm fernbedient werden konnten. Beim Messgerät gab es dann zwei oder drei Unterklassen (Voltmeter, Oszilloskop, ...).
Zuletzt geändert von grubenfox am Freitag 27. Januar 2023, 16:24, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Karlirex: beim Code für den konkreten Fall benutzt Du keine Klassen, sondern missbraucht den Namensraum einer Klasse, um Funktionen zu gruppieren.
Endlich mal ein Fall bei dem and falsch benutzt wird:

Code: Alles auswählen

"unprocessed_data" and "hplc" in event.src_path.lower()

Das macht nicht das was Du denkst.
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

Sirius3 hat geschrieben: Freitag 27. Januar 2023, 16:12 @Karlirex: beim Code für den konkreten Fall benutzt Du keine Klassen, sondern missbraucht den Namensraum einer Klasse, um Funktionen zu gruppieren.
Mmmh, stimmt wohl.. das passiert da... Kommt jetzt schnell auf die Liste "Dinge, die man erstaunlicherweise in Python programmieren kann, die man aber besser vermeiden sollte."
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

kurz zurück zu meinem "Messgerät" und den Unterklassen "Voltmeter" und "Oszilloskop" ....
In der Klasse Messgeraet war eine einheitliche Verhalten-Schnittstelle definiert, in den Unterklassen dann die für die unterschiedlichen Geräte notwendigen Befehle damit sich das jeweilige Gerät wunschgemäß verhält

als Pseudocode ungefähr so

Code: Alles auswählen

class Messgeraet(object):
    def initialisierung(self):
        pass # kein Programmcode
    def messung_ausfuehren(self):
        pass # kein Programmcode
    def ergebnis_liefern(self):
        pass # kein Programmcode

class Voltmeter(Messgeraet):
    def initialisierung(self):
        # alle Nachrichten zum Voltmeter schicken damit es sich initialisiert und dann messen kann
    def messung_ausfuehren(self):
        # Messung auslösen
    def ergebnis_liefern(self):
        # Messergebnis vom Voltmeter holen und zurückliefern
        return messergebnis

class Oszilloskop(Messgeraet):
    def initialisierung(self):
        # alle Nachrichten zum Oszilloskop schicken damit es sich initialisiert und dann messen kann
    def messung_ausfuehren(self):
        # Messung auslösen
    def ergebnis_liefern(self):
        # Messergebnis vom Oszilloskop holen und zurückliefern
        return messergebnis

def pruefvorgang_alpha():
    # notwendige Messgeraete erzeugen
    spannungsmesser = Voltmeter()
    oszi = Oszilloskop()

    # und initialiseren
    spannungsmesser.initialisierung()
    oszi.initialisierung()

    # was messen
    spannungsmesser.messung_ausfuehren()
    oszi.messung_ausfuehren()

    # und was ist dabei rumgekommen?
    volt = spannungsmesser.ergebnis_liefern()
    laufzeit = oszi.ergebnis_liefern()

    # ab in's Protokoll damit und fertig...
    messdaten_protokolieren(volt, laufzeit)


def main():
    pruefvorgang_alpha()

if __name__ == '__main__':
    main()
Sinngemäß ähnlich könnte ich mir da auch was im konkreten Fall vorstellen

Code: Alles auswählen

class Device(object):
    def get_xy_data(self, filename):
        pass
    def running(self, filename):
        pass

class DeviceOne(Device):
    def get_xy_data(self, filename):
        with open(filename) as device_one_text_file:
            full_data = device_one_text_file.readlines()
        angel_list = []
        intensity_list = []
        for line in full_data[1::]:
            angel, intensity = line.replace("\n", "").split(' ')
            angel_list.append(float(angel))
            ntensity_list.append(float(intensity))
        return angel_list, intensity_list

    def running(self, filename):
        if not "unprocessed_data" in os.path.dirname(filename):
            if not ".png" in os.path.basename(filename):
                if not ".xlsx" in os.path.basename(filename):
                    angel_list, intensity_list = self.get_xy_data(filename)
                    generate_plot(filename, angel_list, intensity_list , "One")
                                                        
class Chromatograph(Device):
    def get_xy_data(self, filename):
        # das was bei get_xy_data_chromatogramm ausgeführt wird
    def running(self, filename):
        # das was bei running_two ausgeführt wird
und ob ich dann im späteren Programmablauf nun ein Objekt der Klasse DeviceOne habe oder eines von der Klasse Chromatograph, ich kann immer die Methode running oder vielleicht auch get_xy_data aufgerufen
Benutzeravatar
Dennis89
User
Beiträge: 1152
Registriert: Freitag 11. Dezember 2020, 15:13

Hallo,

@grubenfox ich verstehe in deinen zwei Beispiel nicht ganz, wozu du da Klassen verwendest. Keine der Klassen eine eine '__init__' und in keiner wird 'self' verwendet. Zumindest nicht so, wie man das eigentlich kennt.
Das sind eigentlich gute Anhaltspunkte dafür, das man keine Klassen braucht. Wenn man sich während des Programmablaufs zum Beispiel Zustände merken muss, dann würde deine Klasse Sinn machen, denn diesen Zustand kann man an 'self' binden und ihn immer wieder abfragen bzw. updaten. Würde man das nicht machen, müsste man Argumente durch Funktionen reichen, die darin womöglich gar nichts verloren hätten, nur damit man den Zustand nicht "vergisst".

Man merkt dann aber auch schon an Namen wie "Device" und "DeviceOne", dass da vielleicht was mit der Programmstruktur nicht so ganz stimmt. Beide haben Koordinaten, sind das womöglich zwei gleiche Geräte? Dann würde man eine Klasse erstellen und für jedes Gerät eine Instanz im Code benutzen. Da wären wir wieder bei dem Autobauplan, der hier in einem der Beiträge glaube ich auch schon aufgetaucht ist.

Grüße
Dennis
"When I got the music, I got a place to go" [Rancid, 1993]
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

beide Beispiele sind (Pseudocode-)Beispiele mit der Betonung auf die identische anwendungsspezifische Schnittstelle aller Klassen (beim ersten Beispiel die drei Methoden initialisierung, messung_ausfuehren und ergebnis_liefern). Den üblichen restlichen Kram, der wegen Python immer dabei ist, habe mir jetzt beim Beispiel geschenkt.

Es geht hier nur darum dass in der einen Klasse (die jeweilige Basisklasse) nur das Interface für beliebige Geräte festgelegt wird, während dann in den Unterklassen die verschiedenen Arten von Geräten das ganze mit Leben füllen. In Python 1 und Python 2 hätte ich das ungefähr so gemacht (mit einigen Exceptions in der Basisklasse weil deren Methoden nicht zum aufrufen gedacht sind), seit Python 3.0(?) gibt es ja endlich die von mir lang vermissten abstrakten Basisklassen. Damit wären die Basisklassen in der Anwendung dann von der ABC-Klasse abgeleitet.

Jetzt aktuell bei Python 3.10 oder neuer gibt es möglicherweise noch andere Möglichkeiten das abzubilden. Aber ich bin da über den aktuellen Stand der Möglichkeiten nicht wirklich informiert.
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@grubenfox: Ich sehe nicht, dass du da eine Basisklasse benötigst. Statt eine Basisklasse anzulegen, die eine Methode implemetiert, die die dann nichts weiter macht, als einen Fehler zu schmeissen, wenn sie nicht in einer abgeleiteten Klasse überschrieben wurde, vertrau einfach darauf, dass der Aufruf einer nicht vorhandenen Methode sowieso zu einem Fehler führt. Der dann im Zweifelsfall sogar korrekter ist, als ein von dir definierter, nämlich:

Code: Alles auswählen

>>> class Foo:
...     pass
... 
>>> Foo().bar()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
Es bringt irgendwie wenig zusätzlichen Nutzen etwas selbst zu implementieren, was Python schon als Standardverhalten mitbringt.

Befrag außerdem mal Google zum Thema "Duck Typing".
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
grubenfox
User
Beiträge: 422
Registriert: Freitag 2. Dezember 2022, 15:49

Da halte ich einen selbstgemachter Fehler "an diese Funktionalität hat der Programmierer zwar gedacht, sie aber (noch) nicht implementiert" aber für genauer als ein einfaches "dies Attribut ist nicht da".
Das "Duck Typing" war für mich schon immer auch eine Frage nach dem richtigen Interface. Wenn es aussieht wie eine Ente und quakt wie eine Ente, dann ist es auch eine Ente. Ja...
Wenn es aussieht wie eine Ente, beim quaken aber ein "Miau!" ertönt, ist es dann auch eine Ente? Oder wenn es aussieht wie eine Ente und watschelt wie eine Ente, aber gar nicht quaken kann?

Irgendwo sollte dokumentiert sein was man benötigt um noch als Ente zu gelten und da habe ich es lieber wenn da alle notwendigen Elemente irgendwo explizit zusammen und kurz und knapp zu finden sind.
Seit Python 3 endlich ordentlich in einer abstrakten Basisklasse und wie ich gerade in den letzten Tagen hier irgendwo in einer benachbarten Diskussion gelernt habe, gibt es das mittlerweile auch als Protokoll-Klasse (abgeleitet von typing.Protocol). Aber der Bereich der vom Modul typping behandelt wird (u. a. Annotations seit Python 3.5, Protocol seit 3.8 ) ist mir größtenteils noch zu "neumodischer Kram"... da bleibe ich lieber beim alten DuckTyping.

Ansonsten kann man das Programm ja entweder nur laufen lassen und darauf warten dass es irgendwann knallt weil man vergessen hat seiner selfmade-Ente auch das quaken beizubringen oder man schaut vorher durch alle benutzten externen Bibliotheken wie dort so eine Ente genutzt wird. Das letztere halte ich für extrem unpraktikabel.
Gut, das beachten einer abstrakten Klasse, die für eine Ente das vorhanden sein der Methoden "aussehen" und "quaken" festlegt, kann immer noch zu Laufzeitfehlern führen weil irgendein Programmierer in seinem Modul auch Python-Enten nutzt und irgendwo dann z.B. ente.flieg() aufruft. Gegen solche unerwarteten Gemeinheiten ist man nun gar nicht gefeit..
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@grubenfox: Du musst dir vor Augen führen, welches Problem OOP zu lösen versucht. Das ist das Problem polymorpher Funktionen. Und die Lösung, die OOP präsentiert, sind eben Objekte mit Methoden, die polymorph überladen sind. Das kann man aber auch ganz anders lösen. Haskell zB. löst das anders als Python und Clojure nochmal anders. In Python braucht man keine Basisklasse um Polymorphismus zu implementieren. Dazu braucht man nichtmal Klassen:

Code: Alles auswählen

>>> def foo(n):
...     print(f"foo: {n}")
... 
>>> def bar(n):
...     for i in range(n):
...         print("bar: 1")
... 
>>> funcs = [foo, bar]
>>> 
>>> funcs[0](3)
foo: 3
>>> funcs[1](3)
bar: 1
bar: 1
bar: 1
Eine abstrakte Basisklasse braucht man in Python nicht, auch nicht zu Dokumentationszwecken. Wenn man etwas dokumentieren will, muss man das nicht umständlich über Vererbungshierarchien machen, sondern man schreibt einfach einen doc string. Eine ABC kann nützlich sein, wenn man sein Python-Programm mit Type-Annotations versehen hat und eine statische Typprüfung darüber laufen lässt, etwa mittels mypy. Tust du das?

Mein Fazit: Am Besten, du lässt dich auf Python ein, statt gegen die Sprache zu arbeiten.
In specifications, Murphy's Law supersedes Ohm's.
Benutzeravatar
__blackjack__
User
Beiträge: 13063
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Karlirex: „Sehr funktional“ ist ja nicht synonym mit „unklar“ oder „wenig Klarheit“. Also ausser für Leute die damit nicht können oder keine oder wenig Erfahrung damit haben. Das würde ich also erst einmal nicht als Kritik gelten lassen.

Zum Quelltext: Ausser `CopyFileEventHandler` sind die Klassen dort falsch, weil das gar keine Klassen sind. Ohne `__init__()` und mit keiner ”Methode” die `self` verwendet ist das einfach nur Missbrauch von ``class`` um normale Funktionen zu gruppieren. Das Mittel dafür sind in Python Module. Also weg mit beiden “Klassen“ — das sind einfach nur ein paar Funktionen.

`os.path` & Co würde man heute mit `pathlib` ersetzen. Aber auch wenn man bei `os.path` bleibt, dann würde man nicht *doch* noch mal wieder systemspezifische Trennzeichen einbauen. Das ist ja mit ein Grund warum man Pfade nicht einfach als Zeichenketten behandeln darf, das da Regeln gelten damit das gültige Pfade sind, die sich zudem auch noch von System zu System unterscheiden können.

Auch die Tests die darauf gemacht werden als wenn das einfach Zeichenketten wären, helfen nicht so wirklich beim Verständnis wie das alles tatsächlich aussieht. Ein Test auf ``".png" not in basename`` ist komisch, weil man jetzt nicht weiss ob sich das vielleicht tatsächlich nur auf *Endungen* beziehen soll und der Test einfach nur falsch ist, oder ob tatsächlich geprüft werden soll ob ".png" *irgendwo* *in* dem Namen nicht vorkommen soll.

`angel_list` ist lustig. 😀 Ich vermute mal das sollte `angle` heissen. Oder wie werden Engel repräsentiert und geplottet?

Grunddatentypen wie `list` haben in Namen nichts zu suchen. Das ändert man gerne mal oder man schreibt Code der gar nicht zwingend Listen erfordert, sondern auch mit Arrays, oder gar beliebigen iterierbaren Objekten funktionieren würde, und schränkt das im Namen unnötig auf Listen ein.

Beim öffnen von Textdateien sollte man die Kodierung explizit angeben und nicht hoffen, die ”geratene” wird schon passen.

``replace("\n", "")`` ist bei Zeilen etwas irreführend und auch ineffizient. Man will ja nicht irgendwo in der Zeile Zeichen entfernen, sondern am Ende. Dafür gibt es `rstrip()`. Ist aber auch überhaupt nicht notwendig wenn der Teil mit dem Zeilenendezeichen dann an `float()` verfüttert wird, weil `float()` führende und folgende „whitespace“-Zeichen einfach ignoriert.

`full_data` würde ich `lines` nennen, dann wird beim lesen klarer was dahinter steckt.

In `get_xy_data_chromatogramm()` halten sich viele Namen nicht an die übliche Schreibweise (klein_mit_unterstrichen) und es wird auch viel mit „wir definieren mal Namen auf Vorrat die erst in späteren Blöcken verwenden werden“ gearbeitet. Das macht Quelltext undurchsichtiger wenn nicht der Code zusammen steht der zusammen gehört, sondern man sich den aus verschiedenen Blöcken zusammensuchen muss. Das macht es auch schwerer Teile in Funktionen herauszuziehen. Denn wenn man sich die Funktion anschaut, fällt auf, dass da viermal fast das gleiche gemacht wird, nur mit anderen Zeichenketten als Markierungen und Offsets für den Anfang und das Ende der Daten. Und in `get_mass_data_chromatogramm()` noch mal ähnlicher Code.

In `pdf_file_to_txt()` sind die `Object`-Zusätze in den Namen überflüssig. In Python ist jeder Wert ein Objekt. Das könnte/müsste man dann also an *jeden* Namen mit anhängen, es liefert aber überhaupt keinen Mehrwert für den Leser. Die Funktion ist auch reichlich umständlich. Das Reader-Objekt nimmt auch einen Dateinamen/Pfad, man muss also nicht selbst eine Datei öffnen. Das ``for page in range(len(pdf.pages)):`` ist total unpythonisch weil `page` dann nur als Index für den Zugriff in `pdf.pages` verwendet wird, statt einfach direkt über die Elemente von `pdf.pages` zu iterieren, ohne den unnötigen umweg über den Index. Der ist auch falsch benannt, denn der Wert ist ja keine `page` sondern nur ein Index. Die Funktion lässt sich mit einer „list comprehension“ in einer einzigen Zeile formulieren.

In `on_created()` wird `what` definiert, aber nirgends verwendet.

Die fehlerhafte Verwendung von ``and`` hat Sirius3 ja schon angesprochen. Da die erste Teilbedingung ja bereits im ``if`` davor geprüft wird, könnte man diese beiden ``if``-Anweisungen mit dort in den Zweig ziehen. Sind das überhaupt unabhängige ``if``-Anweisungen oder sollte die Zweite nicht besser ein ``elif`` sein‽

Bei der `PermissionError`-Protokollmeldung ist mir nicht so ganz klar warum der Pfad mit `dirname()` und `basename()` zerlegt wird und dann in der Meldung beide Teile mit einem \ getrennt ausgegeben werden. Da kann man doch einfach gleich den kompletten Pfadnamen ausgeben.

Im folgenden natürlich komplett ungetesteten Zwischenstand fehlt bei den Pfaden alles wo mit der normale Zeichenkettenmethode `replace()` etwas global irgendwo im gesamten Pfad ausgetauscht wurde, weil mir die allgemeine Lösung mit `Path` dafür *zu* allgemein war, man aus dem Code aber nicht erkennen kann wie man das spezifischer lösen müsste.

Code: Alles auswählen

#!/usr/bin/env python3
import shutil
from pathlib import Path
from time import sleep

import pandas as pd
import PyPDF2
from matplotlib import pyplot as plt


def generate_plot(save_path, x_coordinate, y_coordinate, ending):
    #
    # TODO Use the OOP API of matplotlib instead of the one with global state.
    #
    plt.plot(x_coordinate, y_coordinate)
    plt.savefig(save_path.with_name(save_path.name + f"_{ending}.png"))
    plt.clf()


def parse_float(text):
    return float(text.replace(",", "."))


def parse_columns(lines):
    return list(zip(*(list(map(parse_float, line.split())) for line in lines)))


def parse_columns_between(lines, start_keyword, start_offset, end_keyword):
    start = lines.index(start_keyword) + start_offset
    end = lines.index(end_keyword) - 1
    return parse_columns(lines[start:end])


def get_xy_data(file_path):
    with file_path.open(encoding="ascii") as lines:
        next(lines)  # Skip header line.
        return parse_columns(lines)


def running_one(file_path):
    if not (
        "unprocessed_data" in str(file_path)
        or file_path.suffix in [".png", ".xlsx"]
    ):
        angles, intensities = get_xy_data(file_path)
        generate_plot(file_path, angles, intensities, "One")


def get_xy_data_chromatogramm(file_path):
    with file_path.open(encoding="ascii") as file:
        lines = list(file)

    if "Indentifier" in lines[1]:
        ri_start_keyword, ri_end_keyword = "[One]\n", "[Second]\n"
        uv_start_keyword, uv_end_keyword = "[First]\n", "[Last)]\n"
    else:
        ri_start_keyword, ri_end_keyword = "[OtherOne]\n", "[OtherSecond]\n"
        uv_start_keyword, uv_end_keyword = "[OtherFirst]\n", "[OtherLast]\n"

    ri_times, ri_intensities = parse_columns_between(
        lines, ri_start_keyword, 8, ri_end_keyword
    )
    uv_times, uv_intensities = parse_columns_between(
        lines, uv_start_keyword, 11, uv_end_keyword
    )
    return ri_times, ri_intensities, uv_times, uv_intensities


def get_mass_data_chromatogramm(file_path):
    with file_path.open(encoding="ascii") as file:
        lines = list(file)

    start_keyword, end_keyword = (
        ("[First]\n", "[Last]\n")
        if "Indentifier" in lines[1]
        else ("[OtherFirst]\n", "[OtherLast]\n")
    )
    return parse_columns_between(lines, start_keyword, 3, end_keyword)


def generate_csv(file_path, names, areas, concentrations):
    pd.DataFrame(
        {"Name: ": names, "Area: ": areas, "Konz: ": concentrations}
    ).T.to_csv(file_path / f"{file_path.name}_short.txt")


def running_two(file_path):
    if not (
        "unprocessed_data" in str(file_path)
        or file_path.suffix in [".png", ".xlsx"]
        or "short" not in file_path.stem
    ):
        (
            ri_times,
            ri_intensities,
            uv_times,
            uv_intensities,
        ) = get_xy_data_chromatogramm(file_path)
        generate_plot(file_path, ri_times, ri_intensities, "RI")
        generate_plot(file_path, uv_times, uv_intensities, "UV")
        generate_csv(file_path, *get_mass_data_chromatogramm(file_path))


def pdf_file_to_txt(file_path):
    return [page.extract_text() for page in PyPDF2.PdfReader(file_path).pages]


class CopyFileEventHandler(LoggingEventHandler):
    def on_created(self, event):
        super().on_created(event)
        sleep(0.2)
        source_path = Path(event.src_path)
        if not event.is_directory:
            if "unprocessed_data" in str(source_path):
                try:
                    shutil.copy2(
                        source_path,
                        source_path.with_name(f"generated_{source_path.name}"),
                    )
                except PermissionError:
                    with open("debug.log", "a", encoding="utf-8") as logfile:
                        logfile.write(f"Zugangsfehler: {source_path}\n")
                
                sleep(0.5)
                
                if "hplc" in str(source_path).lower():
                    running_two(source_path)
                #
                # TODO Should this be an ``elif``‽
                #
                if "exported" in str(source_path).lower():
                    running_one(source_path)


### hiernach kommt "nur" die if main und der Start vom Watchdog mit Pathangabe
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
snafu
User
Beiträge: 6736
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

@__blackjack__
In der parse_columns() würde ich aber beide list()-Aufrufe weg lassen. Der innere list()-Auruf ist etwas sinnfrei, wenn man direkt anschließend das Unpacking via ``*`` macht. Und der äußere sollte IMHO bei Bedarf vom Aufrufer gemacht werden. Sähe dann eher so aus:

Code: Alles auswählen

def parse_columns(lines):
    return zip(*(map(parse_float, line.split()) for line in lines))
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

Zunächst einmal ein großes Danke an alle für diese ausführliche Diskussion und Erklärungen :)
__blackjack__ hat geschrieben: Samstag 28. Januar 2023, 18:12 Zum Quelltext: Ausser `CopyFileEventHandler` sind die Klassen dort falsch, weil das gar keine Klassen sind. Ohne `__init__()` und mit keiner ”Methode” die `self` verwendet ist das einfach nur Missbrauch von ``class`` um normale Funktionen zu gruppieren. Das Mittel dafür sind in Python Module. Also weg mit beiden “Klassen“ — das sind einfach nur ein paar Funktionen.
Also lieber in ein Modul packen. Dann wäre hier das bessere Stichwort "modular" anstelle von "OOP"?

__blackjack__ hat geschrieben: Samstag 28. Januar 2023, 18:12 `os.path` & Co würde man heute mit `pathlib` ersetzen. Aber auch wenn man bei `os.path` bleibt, dann würde man nicht *doch* noch mal wieder systemspezifische Trennzeichen einbauen. Das ist ja mit ein Grund warum man Pfade nicht einfach als Zeichenketten behandeln darf, das da Regeln gelten damit das gültige Pfade sind, die sich zudem auch noch von System zu System unterscheiden können.

Auch die Tests die darauf gemacht werden als wenn das einfach Zeichenketten wären, helfen nicht so wirklich beim Verständnis wie das alles tatsächlich aussieht. Ein Test auf ``".png" not in basename`` ist komisch, weil man jetzt nicht weiss ob sich das vielleicht tatsächlich nur auf *Endungen* beziehen soll und der Test einfach nur falsch ist, oder ob tatsächlich geprüft werden soll ob ".png" *irgendwo* *in* dem Namen nicht vorkommen soll.
Es soll auf den Dateityp geprüft werden. Also auch eher mit dem pathlib.suffix lösen.
__blackjack__ hat geschrieben: Samstag 28. Januar 2023, 18:12 Grunddatentypen wie `list` haben in Namen nichts zu suchen. Das ändert man gerne mal oder man schreibt Code der gar nicht zwingend Listen erfordert, sondern auch mit Arrays, oder gar beliebigen iterierbaren Objekten funktionieren würde, und schränkt das im Namen unnötig auf Listen ein.
Benennt man das dann dennoch etwas unterscheidbar von Beispielsweise der Datei oder dem Einzelobjekt? Oder würde da wirklich etwas reichen, wie "angles" statt "angle_list"?
__blackjack__ hat geschrieben: Samstag 28. Januar 2023, 18:12 In `pdf_file_to_txt()` sind die `Object`-Zusätze in den Namen überflüssig. In Python ist jeder Wert ein Objekt. Das könnte/müsste man dann also an *jeden* Namen mit anhängen, es liefert aber überhaupt keinen Mehrwert für den Leser. Die Funktion ist auch reichlich umständlich. Das Reader-Objekt nimmt auch einen Dateinamen/Pfad, man muss also nicht selbst eine Datei öffnen. Das ``for page in range(len(pdf.pages)):`` ist total unpythonisch weil `page` dann nur als Index für den Zugriff in `pdf.pages` verwendet wird, statt einfach direkt über die Elemente von `pdf.pages` zu iterieren, ohne den unnötigen umweg über den Index. Der ist auch falsch benannt, denn der Wert ist ja keine `page` sondern nur ein Index. Die Funktion lässt sich mit einer „list comprehension“ in einer einzigen Zeile formulieren.
Nehme ich so als Kritikpunkt auf, habe dies aber aus der Doku vom Modul so gefunden: https://pypdf2.readthedocs.io/en/3.0.0/ ... tions.html
__blackjack__ hat geschrieben: Samstag 28. Januar 2023, 18:12 Bei der `PermissionError`-Protokollmeldung ist mir nicht so ganz klar warum der Pfad mit `dirname()` und `basename()` zerlegt wird und dann in der Meldung beide Teile mit einem \ getrennt ausgegeben werden. Da kann man doch einfach gleich den kompletten Pfadnamen ausgeben.
Beim Nachlesen ist mir dies tatsächlich auch aufgefallen, danke.
Sirius3 hat geschrieben: Freitag 27. Januar 2023, 16:12 @Karlirex: beim Code für den konkreten Fall benutzt Du keine Klassen, sondern missbraucht den Namensraum einer Klasse, um Funktionen zu gruppieren.
Endlich mal ein Fall bei dem and falsch benutzt wird:

Code: Alles auswählen

"unprocessed_data" and "hplc" in event.src_path.lower()

Das macht nicht das was Du denkst.
Nicht? Es soll eigentlich beides prüfen :? Ist ein elif da die richtige Wahl oder mit ifs nacheinander verschalten? Denn sobald eins davon nicht gilt, soll der Fall nicht durchgeführt werden.

Abschließend noch die Frage, wie ich denn erkenne, ob ich vllt doch eine Klasse brauche, mit _init_, self etc. bzw. wie ich das ggf. besser verstehen kann.

Nochmal danke an alle, grüße
Karlirex
Karlirex
User
Beiträge: 126
Registriert: Dienstag 18. August 2020, 18:27

Karlirex hat geschrieben: Montag 30. Januar 2023, 08:26
__blackjack__ hat geschrieben: Samstag 28. Januar 2023, 18:12 Bei der `PermissionError`-Protokollmeldung ist mir nicht so ganz klar warum der Pfad mit `dirname()` und `basename()` zerlegt wird und dann in der Meldung beide Teile mit einem \ getrennt ausgegeben werden. Da kann man doch einfach gleich den kompletten Pfadnamen ausgeben.
Beim Nachlesen ist mir dies tatsächlich auch aufgefallen, danke.
Beim Testen deines Codes, fiel mir wieder auf, warum ich das mit dem \\ gemach habe, scheinbar wird ohne einfach unendlich oft eine weitere Datei erzeugt, die dann ggf. "generated.generated.generated..." heißt, mit dem weg, passiert dies nicht.
Antworten