Trace von Statements weche durchlaufen werden

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
franze_m
User
Beiträge: 11
Registriert: Donnerstag 9. März 2023, 20:27

Hallo,
gibt es denn keinen Statementtrace den ich anschalten kann, wo nur die Codezeilen meines Programmes angezeigt werden ?

Mit /usr/lib/python3.9/trace.py --trace

werde ich mit Ausgaben zugemüllt, das ich keinen Durchblick mehr habe. Mein Programm ist etwas komplexer und durchläuft mehrere Schleifen. Ich verwende sonst Pycham und komme mit dem Debug von Einzschritten nicht mehr weiter.


--- modulename: subprocess, funcname: __del__
subprocess.py(1046): if not self._child_created:
subprocess.py(1049): if self.returncode is None:
subprocess.py(1055): self._internal_poll(_deadstate=_maxsize)
--- modulename: subprocess, funcname: _internal_poll
subprocess.py(1848): if self.returncode is None:
subprocess.py(1871):

Mein Programm hat warscheinlich einen Fehler beim Einrücken der Codezeilen, so das es ohne Fehler unerwartet endet.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn dein Programm Probleme mit Einrueckungen hat, wie kommt das denn am Start vorbei, ohne genau zu benennen, wo es hakt? Oder rufst du den fraglichen Code mit subprocess auf?
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@__deets__: Probleme mit Einrückung muss ja kein Syntaxfehler sein, sondern das da irgendwo semantisch falsch eingerückt ist. Vielleicht auch mit Tabs gemischt, so dass es für menschliche Leser anders aussieht als für Python.

@franze_m: Einzelschittdebugger und Trace finde ich suspekt. So etwas braucht man in Python so gut wie nie, sofern man nicht Python-Code schreibt der eigentlich nur verkleidetes Pascal oder etwas aus der Ära ist. Um was für Probleme handelt es sich denn, die man durch Unit-Tests nicht eingrenzen kann?

Die Startzeile für `trace.py` ist ungewöhnlich. Das schreibt man normalerweise so ``python3.9 -m trace --trace``, da muss man nicht für wissen wo die ``trace.py`` letztendlich installiert ist.

Das Programm hat ja ein paar Optionen, zum Beispiel das ignorieren von Verzeichnissen oder Modulen. Damit könntest Du versuchen die Standardbibliothek auszublenden.

Als Alternative zu `trace` könntest Du auch mal https://pypi.org/project/snoop/ anschauen.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
franze_m
User
Beiträge: 11
Registriert: Donnerstag 9. März 2023, 20:27

Wenn ich wüsste wo es hakt, wäre es behoben.
Das Programm läuft in eine Unterfunktion, kommt da heraus und endet, ohne das ich die entsprechenden Werte sehe, die die Funktion liefern sollte.

#
def wordnr(words, search_word):
# find position of search_word in words
words = string.split()
try:
n = words.index(search_word)
except ValueError:
n = -1
return n

#
def read_loader():
# Huawei Lader auslesen
ov = 0
oc = 0
ocm = 0
op = 0
for l in range(num_loader):
buffer = rloader.replace("$", str(l))
out = subprocess.getoutput(buffer)
if len(out) == 0 :
return 0,0,0,0 # Error read Loader
res = out.replace('\n', ' ')
arr = res.split()
x = wordnr(res, "Output_Voltage")
print(x)
if x > -1 :
ovx = arr()[x + 1]
#ovx = res.split()[22]
#ovx = ovx.replace('V', '')
#ovx = ovx.strip()
ocx = res.split()[25] # Huawei Output Current
ocx = ocx.replace('A', '')
ocx = ocx.strip()
ocmx = res.split()[27] # Huawei Output Max. Current
ocmx = ocmx.replace('A', '')
ocmx = ocmx.strip()
opx = res.split()[32]
opx = opx.replace('W', '')
opx = opx.strip()
if not (opx.replace('.', '', 1).isnumeric()):
opx = int(0)
else:
opx = int(float(opx))
# werte aller loader mitteln
ov = ov + float(ovx)
oc = oc + int(float(ocx))
ocm = ocm + int(float(ocmx))
op = op + int(float(opx))
ov = round(ov / num_loader, 2)
oc = round(oc / num_loader, 2)
ocm = round(ocm / num_loader)
op = round(op / num_loader, 2)
# Ende Huawei Lader auslesen
return ov,oc,ocm,op
#
.
.
Im Hauptcode steht der Aufruf:

ov,oc,ocm,op=read_loader()


Wenn ich mit RExx oder Bash programmiere, kann ich ja auch einen Trace einschalten um den Programmlauf zu verfolgen, das muss doch mit Python auch gehen.
Benutzeravatar
noisefloor
User
Beiträge: 3882
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

bitte den Code in einem Codeblock posten. Der ist erreichbar, indem du im vollständigen Editor hier im Forum auf die </> Schaltfläche klickst. So wie du den Code gepostet hast sind alle Einrückungen weg und der Code ist schlecht lesbar.

Wenn du deinen Code Schritt für Schritt durchgehen willst hilft dir das pdb Modul, siehe https://docs.python.org/3/library/pdb.html und z.B. auch https://pymotw.com/3/pdb/ .

Gruß, noisefloor
Benutzeravatar
__blackjack__
User
Beiträge: 13268
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@franze_m: Eingerückt wird per Konvention vier Leerzeichen pro Ebene.

`wordnr()` enthält einen ziemlich offensichtlichen Fehler, den man durch ausprobieren dieser Funktion leicht feststellen kann. Da hätte ein Unit-Test geholfen.

Auch die `read_loader()`-Funktion enthält einen offensichtlichen Fehler der eine Ausnahme zur Folge hat. Wahrscheinlich wird der momentan durch den Fehler in `wordnr()` verhindert, so dass der Code-Pfad nicht genommen wird, weil das globale `string` (was es nicht geben sollte) kein "Output_Voltage" enthält.

Letztlich ist es aber auch nicht sinnvoll Ausnahmen durch magische Fehlerwerte zu ersetzen. Insbesondere wenn der Fehlerwert auch noch an vielen Stellen als reguläres Ergebnis verwendet werden kann, ohne dass das sofort auffallen muss, und somit leicht falsche Ergebnisse entstehen können.

Der Name der Funktion ist auch nicht gut. Man sollte nicht Deutsch und English mischen. An sich ist das schon nicht gut, aber innerhalb von *einem* Namen mehrere Sprachen ist noch unübersichtlicher. Zumal das auch nicht die Nummer liefert, sondern den Index. Bei Nummer denken die meisten Leser die Zählung beginnt bei 1 und nicht bei 0 wie bei einem Index. Und dann sollten Funktionsnamen *Tätigkeiten* beschreiben, damit man sie leichter von eher passiven Werten unterscheiden kann.

Statt `trace` sollte man besser testbaren, sauberen Code schreiben. Funktionen bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben.

Namen sollten keine kryptischen Abkürzungen enthalten, oder gar nur daraus bestehen.

Eine Funktion mit lokalen Namen wie 'arr', 'l', 'oc', 'ocm', 'ocmx', 'ocx', 'op', 'opx', 'out', 'ov', 'ovx', 'res', und 'x' versteht man nicht und das lädt auch zu schwer zu findenen Fehlern geradezu ein.

`arr` ist neben der Abkürzung auch kein sinnvoller Name für eine Liste. Listen sind keine Arrays, oder anders herum: es gibt in Python auch Arrays und man meint damit meistens Numpy-Arrays, darum ist das verwirrend Listen so zu nennen.

`subprocess.getoutput()` sollte man nicht verwenden weil das eine Shell zwischen das Programm und das externe Programm setzt, die Probleme machen kann auf die man gut verzichten kann. Wie man das konkret durch `subprocess.run()` ersetzt, hängt davon ab wie `rloader` aussieht.

Das ``rloader.replace("$", …)`` sieht so aus als wenn da Templating selbst programmiert, wofür es schon mehrere Lösungen fertig gibt. Im einfachsten Fall die `format()`-Methode auf Zeichenketten.

An der Stelle möchte man auch das auslesen eines einzelnen Laders aus der Funktion heraus ziehen, damit man das leicht einzeln testen kann.

Das ersetzen von Zeichenenden durch Leerzeichen ist unnötig weil `split()` ohne Argumente sowohl an Zeilenumbrüchen als auch an Leerzeichen aufteilt. An allen Whitespace-Zeichen um genau zu sein.

Es ist ineffizient die gleiche Zeichenkette immer wieder aufzuteilen um nur ein einzelnes Element zu nehmen und die Liste wieder zu verwerfen.

`ovx` wird potentiell gar nicht definiert.

Das ersetzen von Einheiten wie "V" und "A" sollte eher nicht mit `replace()` passieren, sondern mit `rstrip()`. Das beschreibt besser was da wo entfernt werden soll.

``int(0)`` ist unnötig kompliziert für einfach nur ``0``.

Bei der Leistung ist der `isnumeric()`-Test komisch. Einfach umwandeln und entsprechend reagieren wenn das auf die Nase fällt statt vorher einen Text zu machen der beim Umwandeln sowieso schon inklusive ist.

Statt eines Tupels würde ich hier einen eigenen Datentyp für das Ergebnis schreiben. Einmal damit die Komponenten einen sinnvollen Attributnamen bekommen, aber auch weil hier Addition von Ergebnissen und die Division durch einen skalaren Wert das Programm vereinfachen können.

Zwischenstand (ungetestet):

Code: Alles auswählen

import subprocess

from attr import attrib, attrs


@attrs(frozen=True)
class Output:
    voltage = attrib(default=0)
    current = attrib(default=0)
    max_current = attrib(default=0)
    power = attrib(default=0)

    def __add__(self, other):
        return Output(
            self.voltage + other.voltage,
            self.current + other.current,
            self.max_current + other.max_current,
            self.power + other.power,
        )

    def __div__(self, scalar):
        return Output(
            self.voltage / scalar,
            self.current / scalar,
            self.max_current / scalar,
            self.power / scalar,
        )


def read_loader(index):
    try:
        result = subprocess.run(
            [
                "the_command",
                "--option",
                str(index),
                "--maybe-some-other-option",
            ],
            stdout=subprocess.PIPE,
            check=True,
            text=True,
        )
    except subprocess.CalledProcessError:
        #
        # TODO Add some logging so searching for bugs would be easier/possible.
        #
        return Output()

    words = result.stdout.split()
    try:
        try:
            i = words.index("Output_Voltage")
        except ValueError:
            voltage = 0
        else:
            voltage = float(words[i + 1].rstrip("V"))

        current = int(float(words[25].rstrip("A")))
        max_current = int(float(words[27].rstrip("A")))
        try:
            power = int(float(words[32].rstrip("W")))
        except ValueError:
            power = 0

        return Output(voltage, current, max_current, power)

    except (IndexError, ValueError):
        #
        # TODO Add some logging so searching for bugs would be easier/possible.
        #
        return Output()


def read_loaders(loader_count):
    return sum(map(read_loader, range(loader_count)), Output()) / loader_count
Das parsen der Ausgabe vom externen Programm kann man sicher etwas verständlicher und robuster mit regulären Ausdrücken machen. Und zum leichteren Testen würde man das auch besser in eine eigene Funktion herausziehen.

@noisefloor: Laut Ausgangsbeitrag kommt franze_m mit dem Debugger von PyCharm nicht mehr weiter — da wird pdb IMHO nicht besser sein.
Please call it what it is: copyright infringement, not piracy. Piracy takes place in international waters, and involves one or more of theft, murder, rape and kidnapping. Making an unauthorized copy of a piece of software is not piracy, it is an infringement of a government-granted monopoly.
franze_m
User
Beiträge: 11
Registriert: Donnerstag 9. März 2023, 20:27

Danke, das muss ich erst einmal verdauen.
Benutzeravatar
snafu
User
Beiträge: 6754
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

franze_m hat geschrieben: Dienstag 31. Oktober 2023, 18:54 Danke, das muss ich erst einmal verdauen.
Die Hilfe wäre konkreter und vielleicht aufgrund von Tools mit weniger Aufwand zu programmieren, wenn du verrätst, welches Gerät von Huawei du hierbei ansprechen möchtest. Eine Suche auf PyPi nach dem Stichwort Huawei (https://pypi.org/search/?q=huawei) ergibt aktuell 335 Projekte. Natürlich werden die alle eine unterschiedliche Qualität haben, aber vielleicht ist ja ein passendes Paket dabei, was dich bei deinem Vorhaben unterstützt.

Selten muss man so niederschwellig ansetzen, wie du es hier zeigst. Und du wirkst nicht gerade wie ein fortgeschrittener Python-Programmierer mit deinen Code-Snippets. Auch nicht so als hättest du das Problem schon wirklich erfasst. Und wer nicht weiß, wie er die richtigen Fragen im Forum stellt, der wird auch an Google und anderen Quellen sich mit hoher Wahrscheinlichkeit die Zähne ausbeißen. Das sage ich nicht, um dich zu ärgern, sondern zur Ermutigung dass du mal dein tatsächliches Problem beschreibst.
Antworten