Forum: Projekte & Code Python SCPI - Keithley DMM6500 u.ä.


von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

ich denke, ich kann mein Pythonprogramm, für Keithley DMM6500 u.ä. 
Messgeräte, auf die Menschheit loslassen. Ich habe mich bemüht es C++ 
ähnlich mit Klasse (Modul) aufzubauen, damit es leicht erweiterbar ist 
bzw. für verwandte Messgeräte mit ähnlichen SCPI Syntax anpassbar 
bleibt.

Das Modul 'KeithleyDMM6500SCPI.py' stellt die SCPI Klasse für Keithley 
mit den nutzbaren Methoden dar .

Die '_FUNCTION_LIST' enthält derzeit Einträge die ich für mich aktuell 
für wichtig halte um irgendwas messen zu können. Das wird sich je nach 
Bedarf mit der Zeit erweitern. Die erste Spalte 'name' darin enthält den 
Namen um an den eigentlich SCPI Syntax zu kommen ohne kryptische 
Indexnummern verwenden zu müssen. Die 2. Spalte 'command' enthält den 
eigentlichen SCPI Syntax laut Manual. Die Einträge kann man jederzeit 
erweitern.

Derzeit am Ende des Moduls sind Methoden die diese Einträge in lesbarer 
Form verwenden bzw. dem eigentlichen Programmaufruf außerhalb der Klasse 
zur Verfügung stellen.

Die Methode '_getMode' übersetzt nur die Funktionsauswahl wie im Display 
sichtbar was man messen möchte in einen Teil des SCPI Syntax, mit der 
bei Notwendigkeit der Syntax zur Übertragung ans Messgerät 
zusammengebaut wird.

Im Hauptprogramm 'SCPI Keithley DMM6500.py' beginnt es mit den üblichen 
Zeilen. Ressourcen anlegen und Verbindung zum Messgerät herstellen.

2 Objekte anlegen in einer Struktur. Jeweils für den SCPI Syntax (obiges 
Modul) Objekt-Member 'scpi' und einmal für die Verbindung zum Messgeräte 
Objekt-Member 'line'. Beide sind über die Instanz 'keithley' definiert. 
Hätte ich mehrere SCPI Messgeräte würden hiermit mehrere Instanzen der 
Klasse Device angelegt werden.

Nach Start erfolgt zu Beginn ein Uhrenvergleich. Ist die Differenz 
größer 1s wird das Messgerät frisch auf Systemzeit gesetzt. Theoretisch 
müsste keine Batterie mehr gewechselt werden. Ich hatte festgestellt das 
die interne RTC schon nach paar Wochen um sehr viele Sekunden falsch 
geht.

Danach wird ein String zusammengebaut der aus dem aktuellen Datum und 
Uhrzeit besteht um damit einen Dateinamen anzulegen der das Logfile ist. 
Das heißt mit jedem Start des Programms wird, sofern eingestellt, ein 
neues Logfile geschrieben mit aktuellen Zeitstempel im Dateinamen.

Danach folgen benötige Methoden die mittels SCPI Objekt Methoden 
funktionieren.

Das eigentliche Programm beginnt mit:
- Logdatei anlegen
if SAVE_DATA:

Oder noch paar Systeminfos wenn gewünscht Zeilen darüber.

Danach folgen verschiedene Kombinationsbeispiele der Funktionen.

- 403 - Buffer im Messgerät löschen,
- 404 - Messungfunktion auf normale Widerstandsfunktion vorauswählen,
- 405 - Vorauswahl auch ans Messgerät übertragen und damit endgültig 
einstellen

- 407 - da vorher nichts anderes eingestellt wurde, erfolgen mit 
Sampling 30 Einzelmessungen nacheinander mit einer Pause von 0s.
- 408 - die erfolgten Messung werden aus dem Buffer gelesen und ins 
Logfile geschrieben.
Erfolgt danach keine Löschung des Buffers, verbleiben diese im Buffer. 
Es wird intern ein Index mitgeführt, damit immer nur die noch nicht 
ausgelesenen Buffereinträge im Logfile landen und es damit keine 
doppelte Auslesung gibt. Sieht man im Logfile wenn man das auslesen 
zwischen den Messung macht oder erst am Ende von allen.

- 410 - der Average Mode 'Repeat' wird eingestellt mit Filterfaktor 20. 
20 interne Messungen werden gemacht, um daraus einen gefilterten Wert 
auszugeben bzw. in den Buffer zu schreiben.
- 411 - es werden 10 Average Messungen gemacht mit je 1s Pause 
dazwischen
- 412 - Average Mode wird deaktiviert
- 413 - die bis dahin noch nicht gespeicherten Einträge werden aus dem 
Buffer ins Logfile geschrieben

- 415 - Buffer wird noch nicht gelöscht
- 416 bis 418 - es werden wieder 3 normale Einzelmessungen gemacht
- 419 - die bis dahin noch nicht gespeicherten Einträge werden aus dem 
Buffer ins Logfile geschrieben

- 422 - Änderung der Messfunktion auf 4 Draht Widerstandsmessung
- 423 - Übertragung der neuen Einstellung ans Messgerät

- 425 - Average Mode mit Filterfaktor 15 wird aktiviert
- 426 bis 428 - erneut 3 Einzelmessungen im Average Mode
- 429 - Average Mode wird abgeschalten
- 430 - die bis dahin noch nicht gespeicherten Einträge werden aus dem 
Buffer ins Logfile geschrieben

Am Ende sauberen Endzustand herstellen. Vorsorglich Average Mode 
ausschalten, falls man es vergisst und bei Bedarf Buffer löschen. Datei 
vorsorglich nochmal schließen falls noch nicht automatisch geschehen. 
Warten bis alle restlichen Kommandos verarbeitet sind, lokalen 
kontinuierlichen Messmodus herstellen und Verbindung trennen.

Die Formatierungen für das Logfile war eine größere Baustelle, damit 
Excel den Datenimport machen kann. Etwas Nachhilfe bleibt erhalten. 
Bspw. erkennt Excel nicht das Datumsformat automatisch. Warum auch 
immer. Beim Zeitstempel werden die Millisekunden abgeschnitten aber 
bleiben zum Glück nur unsichtbar erhalten. Die ersten 10 Dummyeinträge 
dienen dazu damit Excel die generelle Datenformatierung mit allen 
Spalten erkennt. Die Dummyzeilen sind schnell gelöscht, wer mit Excel 
die Messwerte weiter verarbeiten möchte. Im Logfile stehen auch weitere 
Informationen drin. Damit kann man auch nachträglich herauslesen was wie 
in welcher Reihenfolge gemessen wurde, auch wenn man den Buffer erst am 
Ende nach allen Messungen ausliest.

Ob man die Funktionen changeFunction() und setFunction() zusammenführen 
sollte, da bin ich mir bis heute unschlüssig.

Was man im Excel während und nach dem Import machen?

Daten > Daten abrufen > Aus Datei > Aus Text/CSV importieren,
'65001:Unicode UTF-8' kontrollieren/auswählen,
Trennzeichen Tabstopp auswählen,
Daten transformieren auswählen (nicht sofort laden),
im sich öffnenden Power Query Editor die Datumsspalte auswählen,
Datentyp 'Datum' statt 'Ganze Zahl' auswählen und 'Aktuelle ersetzen'
anschließend 'schließen & laden'
zu guter Letzt Spalte vom Zeitstempel auswählen
und das Zellformat Benutzerdefiniert auf hh:mm:ss,000 ändern,
mehr als 3 Nachkommastellen funktioniert nicht,
die Messwertspalte je nach Bedarf einheitlich auf 2 oder 3 Kommastellen

Dürft ihr gern ausprobieren und Verbesserungen vorschlagen.
Danke.

von Udo K. (udok)


Lesenswert?

Ich habe gerade keinen Zugang zum Keithley, werde es aber bei 
Gelegenheit ausprobieren.

Ich bin nicht das grosse Programmiergenie und finde deine Skripte auf 
den ersten Blick recht kompliziert.
Gefühlt macht der Initialisierungscode 75% aus, das verwirrt und sollte 
in eine Bibliothek oder meinetwegen in eine Klasse in einem anderen File 
rein.

Die uralten HP GPIB Programme mit dem eingebauten BASIC Interpreter sind 
bei weitem leichter lesbar.  Ok, damals war die Welt auch noch 
einfacher.

Gruß, Udo

PS: Du hast noch Sonderzeichen 0x202f drinnen, die werden nicht richtig 
angezeigt ohne passenden Font...

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

es stören die Funktionen oben im Hauptprogramm? Nimmt einem die 
Übersicht? Die dort rauszunehmen wäre ein auslagern von Code. Kann man 
machen, kann ich darüber nachdenken. Das wächst genauso mit wie die 
_FUNCTION_LIST.

Wenn irgendwas zu kompliziert geschrieben ist, dann müsste ich wissen 
wie es einfacher geht. Python ist für mich auch noch recht neu.

Wegen Sonderzeichen. Ich verwende zum programmieren VSC, hier werden 
Sonderzeichen korrekt ausgegeben. Für das Logfile wird explizit 
encoding="utf-8" angegeben. In der Logfile Textdatei sind alle 
Sonderzeichen korrekt. Probiere es bitte aus und sage mir was wo schief 
läuft. Ich sehe es aktuell nicht.

Danke.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Technische Anmerkung:
1
__pycache__
 braucht's nicht im Zip-File. ;-)

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Bin mir nicht ganz sicher, was die diversen Systemstatistiken mit SCPI 
und Messgeräten zu tun haben … nur mal so, "Festplatten" bringt bei mir:
1
zroot/ROOT/default - 98.47 GB gesamt, 93.04 GB frei
2
devfs - 0.00 GB gesamt, 0.00 GB frei
3
procfs - 0.00 GB gesamt, 0.00 GB frei
4
zmisc/compat - 1976.90 GB gesamt, 1974.24 GB frei
5
zjail - 857.64 GB gesamt, 828.24 GB frei
6
zroot/tmp - 95.65 GB gesamt, 93.04 GB frei
7
zmisc/junk - 3312.43 GB gesamt, 1974.24 GB frei
8
zmisc/export/camel - 100.00 GB gesamt, 59.09 GB frei
9
zroot/home - 266.90 GB gesamt, 93.04 GB frei
10
zmisc/ports - 2081.24 GB gesamt, 1974.24 GB frei
11
zroot/var - 96.88 GB gesamt, 93.04 GB frei
12
zmisc/osrc - 1977.19 GB gesamt, 1974.24 GB frei
13
zmisc/obj - 1993.46 GB gesamt, 1974.24 GB frei
14
zroot/usr/local - 202.14 GB gesamt, 93.04 GB frei
15
zmisc/src - 1977.68 GB gesamt, 1974.24 GB frei
16
zjail/bastille - 828.24 GB gesamt, 828.24 GB frei
17
zjail/bhyve - 828.24 GB gesamt, 828.24 GB frei
18
zjail/oredmine - 877.67 GB gesamt, 828.24 GB frei
19
zjail/dudle - 853.08 GB gesamt, 828.24 GB frei
20
zjail/lynn - 950.29 GB gesamt, 828.24 GB frei
21
zmisc/cvs - 1974.26 GB gesamt, 1974.24 GB frei
22
zjail/pbx - 879.43 GB gesamt, 828.24 GB frei
23
zjail/grc - 885.59 GB gesamt, 828.24 GB frei
24
zjail/bastille/cache - 828.24 GB gesamt, 828.24 GB frei
25
zmisc/upload - 10.00 GB gesamt, 8.83 GB frei
26
zjail/bastille/jails - 828.24 GB gesamt, 828.24 GB frei
27
zroot/home/dummy - 93.04 GB gesamt, 93.04 GB frei
28
zroot/var/log - 93.31 GB gesamt, 93.04 GB frei
29
zroot/var/crash - 107.70 GB gesamt, 93.04 GB frei
30
zroot/var/tmp - 93.05 GB gesamt, 93.04 GB frei
31
zroot/db/mailman - 5.00 GB gesamt, 3.66 GB frei
32
zmisc/db/afp - 1974.25 GB gesamt, 1974.24 GB frei
33
zmisc/news - 1974.39 GB gesamt, 1974.24 GB frei
34
zjail/redmine - 860.99 GB gesamt, 828.24 GB frei
35
zjail/bhyve/cerberus - 828.24 GB gesamt, 828.24 GB frei
36
zjail/bastille/backups - 828.24 GB gesamt, 828.24 GB frei
37
zjail/bastille/templates - 828.24 GB gesamt, 828.24 GB frei
38
zjail/bastille/releases - 828.24 GB gesamt, 828.24 GB frei
39
zroot/var/mail - 94.40 GB gesamt, 93.04 GB frei
40
zroot/db/postgresql14 - 119.97 GB gesamt, 93.04 GB frei
41
zjail/bhyve/ubuntu - 828.24 GB gesamt, 828.24 GB frei
42
zmisc/pdf - 1977.55 GB gesamt, 1974.24 GB frei
43
zmisc/poudriere/jails/12_4 - 1974.24 GB gesamt, 1974.24 GB frei
44
zmisc/poudriere/data - 1981.29 GB gesamt, 1974.24 GB frei
45
zjail/bhyve/w10 - 828.24 GB gesamt, 828.24 GB frei
46
zjail/bastille/jails/ubuntu - 828.24 GB gesamt, 828.24 GB frei
47
zjail/bastille/logs - 828.24 GB gesamt, 828.24 GB frei
48
zjail/bastille/releases/Ubuntu_2004 - 828.33 GB gesamt, 828.24 GB frei
49
Traceback (most recent call last):
50
  File "<stdin>", line 2, in <module>
51
  File "/usr/local/lib/python3.11/site-packages/psutil/__init__.py", line 2061, in disk_usage
52
    return _psplatform.disk_usage(path)
53
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
54
  File "/usr/local/lib/python3.11/site-packages/psutil/_psposix.py", line 185, in disk_usage
55
    st = os.statvfs(path)
56
         ^^^^^^^^^^^^^^^^
57
PermissionError: [Errno 13] Permission denied: '/zjail/bastille/jails/ubuntu/root'

;-)

ZFS halt.  Man macht sich ein eigenes Volume, wenn man eins braucht.

: Bearbeitet durch Moderator
von Christian M. (likeme)


Lesenswert?

Frage eines Anwenders, der vor 20 Jahren das letzte mal im Quelltext 
gewurschtelt hat, was nutzt ihr für Py für eine Umgebung?

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Christian M. schrieb:
> was nutzt ihr für Py für eine Umgebung?

Privat Emacs, was sonst? ;-)

Dienstlich inzwischen auch meist VScode, weil die Kollegen es nutzen und 
so ein einheitlicher Workflow (insbesondere hinsichtlich des 
integrierten Debuggers) entsteht.

Andere Kollegen mit Windows schwören auf Notepad++.

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> [...] mein Pythonprogramm, [...]

Nunja, im Prinzip ist das ein C++-Programm, das in Python geschrieben 
wurde. Anders gesagt wendest Du das, was Du aus Cpp kennst, auf Python 
an. Dadurch verpaßt Du allerdings gerade das, was Python ausmacht: die 
Lesbarkeit, die Eleganz, die Leichtigkeit, Verständlichkeit und 
Wartbarkeit. ;-/

In Deiner Funktion _getMode() fällt mir auf, daß Du da einen Switch 
benutzt, obwohl ein Dictionary adäquater und lesbarer wäre. Ähnliches 
gilt auch für Deine _FUNCTION_MAP. Als Beispiel möchte ich Dir einmal 
eine pythonischere Variante Deiner _getMode() zeigen:
1
from typing import Optional
2
3
class Kscpi:
4
    modes: dict[str, list[Optional[str], str]] = {
5
        "DCV":    ["V", "VOLTage:DC"],
6
        "DCI":    ["A", "CURRent:DC"],
7
        "ACV":    ["V", "VOLTage:AC"],
8
        "ACI":    ["A", "CURRent:AC"],
9
        "2W":     ["Ω", "RESistance"],
10
        "4W":     ["Ω", "FRESistance"],
11
        "Freq":   [None, "FREQuency"],
12
        "Period": [None, "PERiod"],
13
        "Diode":  ["V", "DIODe"],
14
        "Temp":   ["°C", "TEMPerature"],
15
        "Cap":    ["F", "CAPacitance"]
16
    }
17
18
    def get_mode(self, measure: str) -> str:
19
        try:
20
            self._unit, retval = self.modes[measure]
21
            return retval
22
        except KeyError as e:
23
            self._unit = None
24
            return "unknown"

Ja, man könnte natürlich zuerst abfragen, ob "measure" ein Schlüssel in 
dem Dictionary "self.modes" ist, aber das würde unnötig Rechenzeit 
kosten und es entspricht der Python-Philosophie eher, Dinge einfach 
auszuprobieren und im Fehlerfall die Exception zu händeln. Ja, ich weiß, 
für gestandene Cpp- und Java-Entwickler wirkt das auf den ersten Blick 
oft nicht so schön... :-)

Ich finde es auch nicht so richtig schön, in einer Methode gleichzeitig 
den internen Zustand eines Objekts zu verändern und da obendrein noch 
einen Wert zurückzugeben. Solche Seiteneffekte können die Nutzer der 
Methode verwirren. Python-Funtionen und Methoden können aber mehrere 
Werte zurückgeben, und so würde ich das in meinen Programmen auch 
machen.

Meine Implementierung hat neben der -- wie ich finde -- wesentlich 
besseren Lesbarkeit im Übrigen noch einen weiteren Vorteil: es ist sehr 
einfach, die Daten in self.modes in eine Konfigurationsdatei (YAML, 
JSON, INI) zu legen, und die Klasse dadurch generischer zu machen. 
Ähnliches gilt natürlich auch für die _FUNCTION_LIST, die ich von 
vorneherein als Dictionary implementieren würde -- Du dagegen machst 
erst eine Liste aus Dataclasses, und erzeugst im Konstruktor (Zeile 91) 
dann ein Dictionary daraus. Äh, ja. :-)

Klassen, Funktionen und sogar Typen sind obendrein erstklassige Bürger 
im Python-Universum. Man kann sie als Parameter benutzen, oder etwa auch 
als Value in einem Dictionary, einer Liste, einem Tupel... you get the 
idea. Deswegen ist sowas hier:
1
#!/usr/bin/env python
2
3
class Eins:
4
    def foo(self):
5
        return "Eins.foo()"
6
7
class Zwei:
8
    def foo(self):
9
        return "Zwei.foo()"
10
11
if __name__ == '__main__':
12
    bar = {
13
        "eins": Eins,
14
        "zwei": Zwei
15
    }
16
17
    cls = bar['eins']
18
    inst = cls()
19
    print(inst.foo())
20
21
    print(bar['zwei']().foo())

...absolut gültiger und funktionierender Python-Code -- auch wenn die 
letzte Zeile natürlich nicht so schön zu lesen und deswegen eher etwas 
für schnelle Hacks statt für langfristig zu wartende Programme ist. Aber 
sie zeigt was so ein Python-Interpreter alles zuläßt und ausführt.

Weiterhin legst Du beinahe überall den Datentyp fest, auch wenn Du nur 
eine globale Variable zuweist. Das ist sehr Cpp und in Python eher 
ungewöhnlich. Zumal die Typen in Python gar nicht geprüft werden, 
jedenfalls nicht wie in Cpp zur Kompilierzeit (Kunststück: die 
Kompilation findet ja ohnehin erst zur Laufzeit statt). Beim Zugriff 
achtet Python hingegen sehr wohl auf den Datentyp, weswegen 3 + '3' 
natürlich, anders als etwa in PHP und Perl, eine Exception wirft. Aber 
das passiert, wie gerade bereits erklärt, natürlich erst zur Laufzeit 
(oder mit einem Typchecker wie mypy, aber das ist eine andere Geschichte 
und soll ein andermal erzählt werden).

Außerdem arbeitest Du, ähnlich wie in Cpp, mit Zugriffsmodifikatoren... 
oder, okay: den rudimentären Möglichkeiten, die Python Dir dazu bietet. 
Das macht man in Python normalerweise nicht, zumal: einen echten Schutz 
wie private und protected in Cpp bieten die Dir eh nicht. In Python 
benutzt man sowas normalerweise nur, um einem anderen Entwicker zu 
sagen: "Vorsicht, here be dragons, hier bitte nur herumfummeln wenn Du 
wirklich verstanden hast, was Du da tun möchtest -- und warum".

Und dann sehe ich da noch so ein paar Dinge, wo Du offenbar noch nicht 
sooo genau in die Standardbibliotheken von Python geschaut hast. Deine 
Funktion setFunction() zum Beispiel öffnet bei jedem Aufruf eine Datei, 
schreibt (in Abhängigkeit von der Variablen SAVE_DATA) die neu gesetzte 
Funktion hinein, schließt die Datei wieder... in Deiner Anwendung spielt 
es vermutlich keine Rolle, zumal Du direkt danach eine Sekunde schläfst, 
aber für soche Zwecke enthält Pythons Standardlibrary das Modul 
"logging".

Für Deine Variablen SAVE_DATA, OUTPUT_FORMAT und DEBUG würde ich 
(unabhängig von den Schreibweisen in CAPS, schau bitte mal in PEP 8 [1] 
und PEP 20) vorschlagen, den argparse.ArgumentParser zu benutzen:
1
from argparse import ArgumentParser
2
3
def main():
4
    parser = ArgumentParser(description='Beschreibung')
5
    parser.add_argument('--save-data', '-s', action='store_true', 
6
                        help='was macht --save_data?')
7
    parser.add_argument('--output-format', '-o', 
8
                        choices=['FLOAT', 'EXP'], 
9
                        help='was macht --output-format?')
10
    parser.add_argument('--debug', '-v', action='store_true', 
11
                        help='was macht --debug?')
12
    args = parser.parse_args()
13
14
    print(args.output_format)
15
    print(args.save_data)
16
    print(args.debug)
17
18
19
if __name__ == '__main__':
20
    main()

Dadurch kannst Du die betreffenden Variablen nicht nur im Skript ändern, 
sondern als Kommandozeilenparameter übergeben -- und bekommst obendrein 
automagisch eine schicke Hilfe, wenn jemand das Programm mit "-h" oder 
"--help" aufruft. Nice! :-)

Insofern sehe ich einerseits, daß Du aus der Welt der kompilierten, 
typsicheren Sprachen kommst und versuchst, deren Konzepte auf Python zu 
übertragen. Auf der anderen Seite sehe ich aber auch, daß Du ein 
erfahrener Entwickler bist und nur entweder mehr Erfahrung oder ein paar 
kleine Anstubser brauchst, um Deinen Code eleganter, pythonischer zu 
machen.


[1] https://peps.python.org/pep-0008/
[2] https://peps.python.org/pep-0020/

von Ein T. (ein_typ)


Lesenswert?

Christian M. schrieb:
> Frage eines Anwenders, der vor 20 Jahren das letzte mal im Quelltext
> gewurschtelt hat, was nutzt ihr für Py für eine Umgebung?

Wie bei auch hier den GNU Emacs. Der ist allerdings, ähnlich wie der 
vi(m), mit einer gewissen Eingangshürde verbunden... :-)

von Udo K. (udok)


Lesenswert?

Die Probleme mit dem Sonderzeichen habe ich mit vim und notebook++ unter 
Windows 11.

Schau dir auch mal 
https://pymeasure.readthedocs.io/en/latest/api/instruments/index.html
an. Ich kenne das nicht, aber das DMM6500 wird zumindest unterstützt.

HJK Testcontroller schaut auch brauchbar aus:
https://lygte-info.dk/project/TestControllerIntro%20UK.html

Gruß, Udo

: Bearbeitet durch User
von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo Udo,

ich fange erstmal mit Udo an, ist kürzer.  :-)
Du sprichst vom Logfile? Ich habe auch Win11 und das Logfile kann ich 
bspw. mit dem Texteditor und Notepad++ öffnen und alle Sonderzeichen 
sind korrekt. Eine Bsp. Datei im Anhang. Du müsstest mir sagen was 
geändert werden muss, damit es auch für dein vim funktioniert. Notepad++ 
funktioniert wie gesagt.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Eine Bsp. Datei im Anhang. Du müsstest mir sagen was
> geändert werden muss, damit es auch für dein vim funktioniert.

Ich hab's mir gerade im vim angesehen und nichts Fehlerhaftes 
festgestellt bzw. gefunden. Wo soll das denn sein?
1
:se
2
--- Optionen ---
3
fileformat=dos
4
syntax=python
5
filetype=python
6
fileencoding=utf-8
7
fileencodings=ucs-bom,utf-8,default,latin1

Edit: Oh, im Logfile. Habe ich mangels Gerät nicht. Sollte aber per 
default als utf-8 erstellt werden und dann keine Probleme verursachen.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Du sprichst vom Logfile?

Ich denke, er meint das Omega.

von Norbert (der_norbert)


Lesenswert?

Jörg W. schrieb:
> Veit D. schrieb:
>> Du sprichst vom Logfile?
>
> Ich denke, er meint das Omega.
1
~/tmp$ cat>Y
2
ΩΩ
3
~/tmp$ hd Y
4
00000000  ce a9 ce a9 0a                                    |.....|
5
00000005
6
~/tmp$ ^C
Macht der vi tadellos.
Welches von Elektronikern genutzte System kann denn kein Omega 
darstellen?

: Bearbeitet durch Moderator
von Veit D. (devil-elec)


Lesenswert?

Jörg W. schrieb:
> Veit D. schrieb:
>> Du sprichst vom Logfile?
>
> Ich denke, er meint das Omega.

Natürlich, ist ja ein Sonderzeichen. Nur womit ist unklar. Udo spricht 
leider derzeit in Rätseln. Rätselraten vermutlich im Logfile? Oder 
Terminalausgabe vom Programm? Win11 und Notepad++ kann ich nicht 
bestätigen.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Rätselraten vermutlich im Logfile?

Du hast es ja auch im Sourcecode stehen, und außerdem schrieb er ja nur, 
dass man dafür einen entsprechenden Font da haben muss und der benutzte 
Editor den auch verwenden muss.

Habe das Ding nicht auf Windows ausgepackt, nur auf FreeBSD, da gab's 
natürlich keine Probleme mit der Darstellung.

von Veit D. (devil-elec)


Lesenswert?

Ein T. schrieb:
> Veit D. schrieb:
>> [...] mein Pythonprogramm, [...]
>
> Nunja, im Prinzip ist das ein C++-Programm, das in Python geschrieben
> wurde. Anders gesagt wendest Du das, was Du aus Cpp kennst, auf Python
> an. Dadurch verpaßt Du allerdings gerade das, was Python ausmacht: die
> Lesbarkeit, die Eleganz, die Leichtigkeit, Verständlichkeit und
> Wartbarkeit. ;-/
> ...

Hallo Typ,

gut analysiert. Bin zwar kein Entwickler in dem Sinne, ich sage einmal 
ambitionierter Hobby Mensch. ;-)

Und ja, ich habe das wie gesagt Cpp ähnlich aufgebaut. Datentypangabe 
habe ich später sicherheitshalber hinzugefügt, weil ich Probleme mit dem 
Datentyp bekam, wobei man unter Python einer Variablen alles und jeden 
zuweisen können sollte. So ist wenigstens klar, dass meistens ein String 
zurückkommt. Manchmal bin ich zum casten gezwungen und beim 
zusammensetzen von Strings komischerweise in Teilstrings um mittendrin 
auftretende Zeilenumbrüche entfernen zu können. Oft wird ein 
Zeilenumbruch zu viel erzeugt, was nirgends steht. Dann hat man 
plötzlich mittendrin eine Leerzeile, statt nur eine neue Zeile. Das am 
Rande warum manche Funktionen so lang geworden sind als Entschuldigung. 
;-)

Von Cpp bin ich Typsicherheit gewohnt. Damit tue ich mich ehrlich gesagt 
unter Python noch schwer das es sowas nicht gibt. Deswegen auch der 
Versuch Datentypen anzugeben. Ob das Sinn macht weiß ich noch nicht. 
Beim Lesen der Funktion sollte jedoch jedem sofort ersichtlich sein, was 
Eingabe und Ausgabe erwarten. Das war/ist die Motivation dahinter. :-)

Ich will damit nicht über Python meckern. Man muss sich daran gewöhnen.

Ansonsten ist deine Antwort das was ich an Kritik erwartet habe und mich 
ran setzen kann. Vielen Dank.

von Norbert (der_norbert)


Lesenswert?

Jörg W. schrieb:
> Du hast es ja auch im Sourcecode stehen, und außerdem schrieb er ja nur,
> dass man dafür einen entsprechenden Font da haben muss und der benutzte
> Editor den auch verwenden muss.

Ich glaube Windows 3.1 oder 3.11 (aber ohne fW) konnte so etwas noch 
nicht.
Aber ein halbwegs modernes System? Oder muss man für Deutsche Umlaute 
auch ein spezielles Servicepack installieren?
Demnächst erklärt noch jemand, dass Windows kein großen ẞ kann.

Verlässt gram-gebeugten Hauptes die Bühne nach links. Kopfschüttelnd.

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> gut analysiert. Bin zwar kein Entwickler in dem Sinne, ich sage einmal
> ambitionierter Hobby Mensch. ;-)

Alles gut, ich bin auch aus dem C- und Cpp-Umfeld gekommen, als ich mit 
meiner ersten Skriptsprache Perl angefangen habe. :-)

> Und ja, ich habe das wie gesagt Cpp ähnlich aufgebaut. Datentypangabe
> habe ich später sicherheitshalber hinzugefügt, weil ich Probleme mit dem
> Datentyp bekam, wobei man unter Python einer Variablen alles und jeden
> zuweisen können sollte.
> [...]
> Von Cpp bin ich Typsicherheit gewohnt. Damit tue ich mich ehrlich gesagt
> unter Python noch schwer das es sowas nicht gibt. Deswegen auch der
> Versuch Datentypen anzugeben. Ob das Sinn macht weiß ich noch nicht.
> [...]
> Beim Lesen der Funktion sollte jedoch jedem sofort ersichtlich sein, was
> Eingabe und Ausgabe erwarten. Das war/ist die Motivation dahinter. :-)

Genau DAS ist einer der Gründe, weswegen ich das bei größeren Projekten 
auch mache. Einerseits dokumentiert das gleich die Parameter, so daß ich 
das nicht mehr in extra pydoc-Kommentare schreiben muß. Aber es eröffnet 
noch viel mehr Möglichkeiten, nämlich einerseits die Prüfung meines Code 
mit dem statischen Typchecker Mypy [1,2], und andererseits kann ich 
meinen Code bei Bedarf viel effizienter in ein Binary Executable 
verwandeln. Da gibt es ein paar schicke Compiler, die für Type 
Annotations sehr dankbar sind. :-)

Insofern: ja, Type Annotations haben mehrere Vorteile und für Code, den 
man selbst länger nutzen und pflegen, oder anderen zur Verfügung stellen 
möchte, sollte man sie deshalb auch benutzen. Andererseits machen sie 
den Code nicht unbedingt lesbarer, bieten keinerlei echten Schutz und 
kosten beim Schreiben natürlich ein bisschen Zeit.

Nebenbei bemerkt, ist Python -- im Gegensatz zu Perl und PHP -- 
tatsächlich typsicher. Anders als die meisten kompilierten Sprachen 
bindet es Datentypen jedoch nicht an die Variable, sondern an ihren 
Wert. Darum wirft Python eine Exception (TypeError), wenn Du 3 + '3' 
auszuführen versuchst; Perl und PHP hingegen casten den String einfach 
automatisch in eine Zahl und geben 6 aus.

[1] https://mypy-lang.org/
[2] https://mypy.readthedocs.io/en/stable/

> Ich will damit nicht über Python meckern. Man muss sich daran gewöhnen.

Keine Sorge, wenn Du das öfter benutzt, wirst sicherlich auch Du an den 
Punkt kommen, der Python den Beinamen "ausführbarer Pseudocode" 
eingebracht hat.

> Manchmal bin ich zum casten gezwungen und beim
> zusammensetzen von Strings komischerweise in Teilstrings um mittendrin
> auftretende Zeilenumbrüche entfernen zu können.

Um Zeilenumbrüche und anderen Whitespace aus einem Text zu entfernen, 
gibt es viele Möglichkeiten, von str.replace('\n', '') über 
str.[lr]strip()... Und dann gibt es in Python die Builtin-Funktion 
map(), die eine Funktion auf alle Elemente eines Iterable (Liste, Tupel, 
Set, ...) anwendet und ein map-Objekt zurückgibt, also einen Generator 
(think Cpp Streams). Und am Ende, wenn einem da wirklich gar nichts mehr 
einfällt, hat Python natürlich ein Standardmodul namens regex für, was 
für eine Überraschung: regular Expressions. :-)

> Ansonsten ist deine Antwort das was ich an Kritik erwartet habe und mich
> ran setzen kann. Vielen Dank.

Sehr gerne... wenn Du magst, könntest Du ein öffentliches oder privates 
Git-Repository zum Beispiel bei Codeberg.org (aus Berlin) anlegen oder 
mich das machen lassen, und dann arbeiten wir gemeinsam daran.

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

also ich möchte jetzt nicht auf Udo und Sonderzeichen rumreiten, weil 
Udo wie ich so mitbekomme ein netter Mensch ist. Nur sind mir seine 
Aussagen zu schwammig zu unklar formuliert. Ich kann damit nichts 
anfangen. Entweder gibt es ein Problem oder es gibt kein Problem. Damit 
nichts durcheinanderkommt, pflege ich die genaue Abfolge einzuhalten was 
wann geschrieben wurde.

> PS: Du hast noch Sonderzeichen 0x202f drinnen, die werden nicht richtig
> angezeigt ohne passenden Font...

Kann man als Hinweis verstehen, habe ich so verstanden, dass bei Udo 
Sonderzeichen nicht korrekt dargestellt werden.

> Die Probleme mit dem Sonderzeichen habe ich mit vim und notebook++ unter
> Windows 11.

Hat sich damit bestätigt. Udo sieht keine Sonderzeichen. Genau das kann 
eigentlich nicht sein. Ich gebe im Programm extra das UTF-8 Format an 
für anlegen und speichern des Logfiles. Eben weil ich ohne diese Angabe 
auch Probleme mit dem Sonderzeichen hatte. Da UTF-8 Format standardmäßig 
vorhanden ist, lautet die Frage, warum kann Udos Rechner das nicht?

Speichere einmal bei dir zum Test mein Logfile mit "speichern unter" 
nochmal ab und achte auf die Codierung unten rechts im Fenster. Das 
sollte so schon eingestellt sein für diese Datei. Wenn nicht ändere es 
ab und öffne erneut.

von Veit D. (devil-elec)


Lesenswert?

Udo K. schrieb:
> Die Probleme mit dem Sonderzeichen habe ich mit vim und notebook++ unter
> Windows 11.
>
> Schau dir auch mal
> https://pymeasure.readthedocs.io/en/latest/api/instruments/index.html
> an. Ich kenne das nicht, aber das DMM6500 wird zumindest unterstützt.
>
> HJK Testcontroller schaut auch brauchbar aus:
> https://lygte-info.dk/project/TestControllerIntro%20UK.html
>
> Gruß, Udo

Hallo Udo,

Danke für die Links. Das ist irgendwie unfair. Da hat schon jemand meine 
Arbeit gemacht. :-)

von Udo K. (udok)


Lesenswert?

Veit D. schrieb:
> Danke für die Links. Das ist irgendwie unfair. Da hat schon jemand meine
> Arbeit gemacht. :-)

Ja sorry, ist wirklich unfair :-)  Aber schaue es dir erst mal an.

Das Problemchen mit dem Sonderzeichen ist im Python File in Zeile 376 
das 'NARROW NO-BREAK SPACE' statt dem Leerzeichen in "1 ms".
Wenn ein Windows Font ein Unicode Zeichen nicht enthält wird Font 
Substitution verwendet.  Die meisten Open Source Programme schalten Font 
Substitution nicht ein, schon gar nicht wenn sie aus der Linux Ecke 
kommen.  Wenn vim oder Notepad++ einen (Konsolen) Font verwendet, der so 
ein Zeichen nicht beinhaltet sieht man dann ein Rechteck.  Das sticht 
ins Auge, sonst passiert da nichts schlimmes.  Das kannst du ignorieren.

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo Udo,

ich erahne langsam was du meinst. Es geht nicht um falsch dargestellte 
Zeichen während der Ausgabe meiner gewollten Daten. Es geht rein um die 
Darstellung des reinen Programmcodes. War für mich leider zu undeutlich 
was du dir anschaust. ;-) Nun denn. Will mal nicht so streng sein.

Also wenn ich die Datei SCPI Keithley DMM6500 v0.1.0.py im Notepad++ 
öffne, dann sieht die Kommentarzeile 376 auch bei mir komisch aus was in 
der Klammer steht. Geschrieben ist der Code in VSC. Ich habe folgendes 
probiert.

Original steht in VSC geöffnet das drin in Zeile 376.
1
# Kurze Pause zur Stabilisierung (z. B. 1 ms)
In VSC geändert in
1
# Kurze Pause zur Stabilisierung (zum Bsp. 100 ms)
wird in Notepad++ wie folgt dargestellt.
1
# Kurze Pause zur Stabilisierung (zum Bsp. 100NNBSPms)

Also eine Korrektur hat so keine Wirkung auf fehlerhafte Zeichen.
In VSC Zeile gelöscht, gespeichert, beendet.
In VSC neu geöffnet und Zeile neu getippt, gespeichert, beendet.
In Notepad++ geöffnet. Alles korrekt.

Wer weiß was da beim tippen der Zeile bzw. speichern schief ging. Oder 
es kam ungewollt ein unsichtbares Zeichen rein. Keine Ahnung. Scheint ja 
eine Ausnahme zu sein. Noch mehr solchen Mist sehe ich im Notepad++ 
nicht wenn ich scrolle. Bei einem systematischen Fehler müssten noch 
mehr Zeilen betroffen sein. Ist die Lösung damit zufriedenstellend? 
Geändertes .zip hängt dran.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Ist die Lösung damit zufriedenstellend?

War meiner Meinung nach auch vorher nicht falsch.
Ich bin nach wie vor verblüfft, dass Windows anscheinend noch nicht 
einmal rudimentäre Fonts per Default mitliefert.

Ich habe gerade mal auf meinem System alle Fonts gelistet, welche ein 
NNBSP (u202f) beinhalten.
Bin auf achtzig (80) Fonts/Varianten gekommen. Viele (die meisten) davon 
gehören zur absoluten Basisausstattung 
(DejaVu,Free,Lato,Liberation,Noto).

Da lohnt es sich wohl zwecks Datenaustausch mit Windows-Nutzern den 
geschriebenen Text besser auf 7-Bit ASCII zu limitieren. Oder 
Keilschrift.

von Veit D. (devil-elec)


Lesenswert?

Hallo Typ,

da tun sich Möglichkeiten auf. ;-) Mit Typsicherheit meinte ich folgende 
ungewohnte Möglichkeit der wilden Zuweisungen.
1
var = "String"
2
print(var)
3
var = 1234
4
print(var)
5
6
oder
7
8
var:str = "String"
9
print(var)
10
var = 1234
11
print(var)

In allen Fällen erscheint:
1
String
2
1234

Nur wenn man String mit 1234 addieren möchte muss man casten. :-) :-)

Auch wenn die Links zu fertigen Bibliotheken und Programmen verlockend 
sind, man hat ja dennoch eigene Vorstellungen in der Art der Verwendung. 
Mein Endziel irgendwann soll sein, das ich das Messgerät Keithley 
DMM6500 und Netzteil TTi QL355TP mittels SCPI steuern kann um bspw. eine 
Diodenkennlinie automatisiert erstellen zu können. Also die Daten 
aufnehme und meinetwegen in Excel daraus ein Diagramm erstelle. Dafür 
ist noch eine Menge zu tun.

Um Zeilenumbrüche zu entfernen nutze ich bisher end="" oder .strip(). Es 
wäre nur gut zu wissen warum da überhaupt Zeilenumbrüche drin sind. Wo 
kommen die plötzlich her? Ich musste manche zusammengesetzte Strings 
wieder zerlegen, um zu wissen an welcher Stelle ein .strip() notwendig 
ist.

Codeberg.org ist ein Git-Repository Kopie aber ohne Microsoft Kontrolle? 
Habe mich mit Git… noch nie so richtig befasst, ist mir bis jetzt alles 
zu kompliziert in der Bedienung. Außer das ich auf Github einen Account 
habe für Downloads etc. Aber ich wäre bereit es auszuprobieren und ein 
Projekt anzulegen. Ich überlege mir das einmal. Danke schon einmal für 
das Angebot. Es wäre schon gut jetzt ein Stopp einzulegen um den Code 
aufzuräumen. Bevor dieser Python untypisch verwurstet. Ich kann ja schon 
alles was nichts mit dem Messgerät zu hat rauswerfen.

von Veit D. (devil-elec)


Lesenswert?

Norbert schrieb:
> Veit D. schrieb:
>> Ist die Lösung damit zufriedenstellend?
>
> War meiner Meinung nach auch vorher nicht falsch.
> Ich bin nach wie vor verblüfft, dass Windows anscheinend noch nicht
> einmal rudimentäre Fonts per Default mitliefert.

Hallo Nobert,

das scheint kein Font Problem gewesen zu sein. Ich habe am Font nichts 
geändert. Ich habe praktisch das scheinbar "komische" Leerzeichen vor ms 
entfernt und alles neu getippt. Das scheint kein reines Leerzeichen wie 
in anderen Hunderten Zeilen gewesen zu sein. Warum auch immer. Mit 7-Bit 
ASCII hat man kein Omega o.ä. zur Verfügung.

von Norbert (der_norbert)


Lesenswert?

Ein bisschen präziser sollte man es schon versuchen:
1
#!python
2
var = "1234"
3
print(var, type(var))
4
var1 = 1234
5
print(var1, type(var1))
6
var2 = 1234.0
7
print(var2, type(var2))
8
var3 = var1 == var2
9
print(var3, type(var3))
10
var4 = var == var2
11
print(var4, type(var4))
1
1234 <class 'str'>
2
1234 <class 'int'>
3
1234.0 <class 'float'>
4
True <class 'bool'>
5
False <class 'bool'>

von Norbert (der_norbert)


Lesenswert?

Ja, aber das

> das scheinbar "komische" Leerzeichen

ist tatsächlich ein völlig normales und gültiges Zeichen (narrow non 
breaking space) wie man es in der Textverarbeitung sehr oft findet.
Und es sollte im UTF-8 Umfeld keinesfalls ein Problem verursachen.

> Mit 7-Bit ASCII hat man kein Omega o.ä. zur Verfügung.

Das war natürlich eine leichte Überzeichnung um das Problem zu 
verdeutlichen.

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Norbert schrieb:
>> das scheinbar "komische" Leerzeichen
>
> ist tatsächlich ein völlig normales und gültiges Zeichen (narrow non
> breaking space) wie man es in der Textverarbeitung sehr oft findet.
> Und es sollte im UTF-8 Umfeld keinesfalls ein Problem verursachen.

Andererseits gibt es eben jenseits der Textverarbeitung keinen richtigen 
Grund, sowas in einem Programmtext zu verwenden.

Norbert schrieb:
> Ich bin nach wie vor verblüfft, dass Windows anscheinend noch nicht
> einmal rudimentäre Fonts per Default mitliefert.

Gerade Windows hat sich mit UTF-8 lange Zeit sehr schwer getan. Die 
haben sehr früh mit UTF-16 angefangen, aber dann sehr lange daran 
geklebt, obwohl es erstens für viele Texte Platz verschwendet, 
andererseits aber eben trotzdem einen Teil der Sprachen der Welt nicht 
abdeckt.

Scheint sich inzwischen aber so allmählich zu erledigen.

von Norbert (der_norbert)


Lesenswert?

Jörg W. schrieb:
> Andererseits gibt es eben jenseits der Textverarbeitung keinen richtigen
> Grund, sowas in einem Programmtext zu verwenden.

Aber selbstverständlich. Gerade Python ist prädestiniert, den UTF8 
Zeichensatz innerhalb von Strings zu nutzen. Teilweise sogar außerhalb, 
aber das funktioniert dann wohl nur auf *X Systemen anstandslos.

Jörg W. schrieb:
> Gerade Windows hat sich mit UTF-8 lange Zeit sehr schwer getan. Die
> haben sehr früh mit UTF-16 angefangen,

Wobei die ersten ~ 65000 Zeichen aber doch gleich sind (Nicht in der 
Kodierung, aber der erste Level ist komplett abgedeckt).
Und dazu gehört zB. auch ein NNBSP.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Norbert schrieb:
> Ein bisschen präziser sollte man es schon versuchen:
>
1
#!python
2
> var = "1234"
3
> print(var, type(var))
4
> var1 = 1234
5
> print(var1, type(var1))
6
> var2 = 1234.0
7
> print(var2, type(var2))
8
> var3 = var1 == var2
9
> print(var3, type(var3))
10
> var4 = var == var2
11
> print(var4, type(var4))
>
1
> 1234 <class 'str'>
2
> 1234 <class 'int'>
3
> 1234.0 <class 'float'>
4
> True <class 'bool'>
5
> False <class 'bool'>
6
>

Hallo,

ähmm. Das ändert alles nichts an den Möglichkeiten der bunten Mischung 
an Zuweisungen, egal welchen Datentyp eine Variable hat(te). Der 
Wildwuchs macht eben Python aus. Mit der nicht vorhandenen 
Datentypsicherheit muss man umgehen lernen, wenn man anderes gewohnt 
ist.

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Norbert schrieb:
>> Andererseits gibt es eben jenseits der Textverarbeitung keinen richtigen
>> Grund, sowas in einem Programmtext zu verwenden.
>
> Aber selbstverständlich. Gerade Python ist prädestiniert, den UTF8
> Zeichensatz innerhalb von Strings zu nutzen.

Es ging mir ganz speziell um den "narrow non breaking space", nicht um 
UTF8 allgemein.  Konkret: das "Ω" stelle ich gar nicht in Frage.

Non breaking space an sich ist schon eher was für eine Textverarbeitung, 
in einem technischen Dokument (wie einem Logfile) sehe ich dafür keinen 
Sinn.  Der "normale" NBSP wäre ja aber noch im einfachen 
8-Bit-Zeichensatz drin, wofür aber braucht man hier davon auch noch die 
"narrow"-Variante?

von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Das ändert alles nichts an den Möglichkeiten der bunten Mischung
> an Zuweisungen, egal welchen Datentyp eine Variable hat(te). Der
> Wildwuchs macht eben Python aus.

Ich würde es nicht als Wildwuchs bezeichnen, eher als kompletten 
Kontrollverlust des Programmierers. ;-)

Veit D. schrieb:
> Mit der nicht vorhandenen
> Datentypsicherheit muss man umgehen lernen, wenn man anderes gewohnt
> ist.

Das stimmt. Obschon auch Python Typüberprüfungen durchführen kann, 
sollte es denn erforderlich erscheinen.
1
#!/usr/bin/python3
2
# vim: fileencoding=utf-8: ts=4: sw=4: expandtab:
3
4
def fdivi(a,b):
5
    if type(a) is not float or type(b) is not float:
6
        raise ValueError('Was soll das denn?')
7
    if b == 0:
8
        raise ZeroDivisionError('Ernsthaft?')
9
    return a/b
10
11
# fdivi(1,2)
12
fdivi(1.0,0.0)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

wir/ihr müssen uns jetzt nicht wegen dem NBSP oder NNBSP streiten. Das 
stand auch nicht im Logfile, sondern in der .py Programmdatei in einer 
Kommentarzeile. Das ist Udo "nur" beim anschauen des Programmcodes 
aufgefallen. Das hätte keine Auswirkung auf das Programm gehabt. Wie 
gesagt, wie das dort hinkam weiß ich nicht. Es ist korrigiert und gut 
ist.

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> da tun sich Möglichkeiten auf. ;-) Mit Typsicherheit meinte ich folgende
> ungewohnte Möglichkeit der wilden Zuweisungen.
>
1
> var = "String"
2
> print(var)
3
> var = 1234
4
> print(var)
5
> 
6
> oder
7
> 
8
> var:str = "String"
9
> print(var)
10
> var = 1234
11
> print(var)
12
>
>
> In allen Fällen erscheint:
>
1
> String
2
> 1234
>
> Nur wenn man String mit 1234 addieren möchte muss man casten. :-) :-)

True, aber solche Casts sind häufig ein Hinweis auf ein verkorkstes 
Design. Und
1
var = "String"
2
print(var + str(1234))

gibt "String1234" aus -- das "+" ist nämlich für Zahlen der Additions- 
und für Strings der Konkatenationsoperator. Es gibt jedoch eine 
(selbstverständlich dokumentierte) Ausnahme:
1
print(5 * '-')

gibt "-----" aus. Das kann zum Beispiel dann nützlich sein, wenn man 
schicke Tabellen ausgeben möchte, wobei es auch dafür viel bessere 
Fremdbibliotheken gibt.

Nebenbei bemerkt: mit den Builtin-Funktionen type() findest Du den Typ 
des aktuellen Werts einer Variablen heraus und mit isinstance() kannst 
Du prüfen, ob der aktuelle Wert einer Variablen einem (oder mehreren) 
erwarteten Typen entspricht. Aber bitte tu' Dir und mir einen Gefallen: 
ja, ich weiß, Du bist an Typsicherheit gewöhnt und darauf zu verzichten, 
fühlt sich am Anfang echt merkwürdig an -- aber bitte fang' jetzt nicht 
an und spicke alles in Deinen Programmen mit type()- und 
isinstance()-Aufrufen. In meinen > 15 Jahren mit Python habe ich type() 
bisher nur zu informativen Zwecken und isinstance() allenfalls drei oder 
vier Mal benutzt.

Nebenbei bemerkt: das Modul "builtins" enthält eine Reihe von sehr 
nützlichen Funktionen und Klassen, da sollte man mal hineingeschaut 
haben. :-)

Was Type Hints angeht, gibt es einen lesenswerten Artikel bei 
RealPython: [1]. Die Seite ist ohnehin (neben der offiziellen 
Python-Website [2]) eine der mit Abstand besten Python-Seiten, die ich 
kenne.

[1] https://realpython.com/python-type-checking/
[2] https://www.python.org/

> Auch wenn die Links zu fertigen Bibliotheken und Programmen verlockend
> sind, man hat ja dennoch eigene Vorstellungen in der Art der Verwendung.
> Mein Endziel irgendwann soll sein, das ich das Messgerät Keithley
> DMM6500 und Netzteil TTi QL355TP mittels SCPI steuern kann um bspw. eine
> Diodenkennlinie automatisiert erstellen zu können. Also die Daten
> aufnehme und meinetwegen in Excel daraus ein Diagramm erstelle. Dafür
> ist noch eine Menge zu tun.

Ja, äh... also... Mit dem (externen) Modul "pandas" gibt es sehr 
mächtige Werkzeuge zur Verarbeitung und Visualisierung von Daten, und 
wer das einmal zusammen mit dem  interaktiven Jupyter benutzt hat, der 
möchte danach keine Tabellenkalkulationen mehr haben. :-)

> Um Zeilenumbrüche zu entfernen nutze ich bisher end="" oder .strip(). Es
> wäre nur gut zu wissen warum da überhaupt Zeilenumbrüche drin sind. Wo
> kommen die plötzlich her? Ich musste manche zusammengesetzte Strings
> wieder zerlegen, um zu wissen an welcher Stelle ein .strip() notwendig
> ist.

Die Builtin-Funktion print() hängt automatisch einen Zeilenumbruch an, 
wenn "end=''" nicht übergeben wird.

Tipp: logging-Modul benutzen. Das ist sehr alt, was man an 
Funktionsnamen wie basicConfig() sehen kann (PEP 8, der Styleguide für 
Python, empfiehlt nämlich für Funktions- und Methodennamen snake case, 
also würde die Funktion heute basic_config heißen).
1
# Importe
2
import sys
3
import logging
4
5
# Modul "logging" konfigurieren (da geht noch viel mehr!)
6
logging.basicConfig(
7
    format='%(asctime)s %(levelname)s %(filename)s(%(lineno)d), %(message)s",
8
    stream=sys.stdout,
9
    level=logging.DEBUG
10
)
11
12
# etwas auf dem Loglevel INFO loggen
13
logging.info("Dies ist eine Information!")

loggt auf sys.stdout (std::cout, STDOUT), wenn "stream=..." durch 
"filename=" mit einem Dateinamen ersetzt wird, loggt es in die 
betreffende Datei.

> Codeberg.org ist ein Git-Repository Kopie aber ohne Microsoft Kontrolle?

Ganz genau, der Verein ist in Berlin beheimatet und unterliegt deswegen 
den europäischen und deutschen Vorschriften zum Datenschutz. Die 
Oberfläche ist Forgejo und Gitlab sehr ähnlich.

> Habe mich mit Git… noch nie so richtig befasst, ist mir bis jetzt alles
> zu kompliziert in der Bedienung.

Git möchte ich Dir wärmstens ans Herz legen, das benutze ich ausnahmslos 
immer. Ja, es gibt eine Einstiegshürde, aber der Nutzen überwiegt die 
Kosten sehr, sehr, sehr, sehr (you get the idea) schnell.

> Danke schon einmal für
> das Angebot. Es wäre schon gut jetzt ein Stopp einzulegen um den Code
> aufzuräumen. Bevor dieser Python untypisch verwurstet. Ich kann ja schon
> alles was nichts mit dem Messgerät zu hat rauswerfen.

Ja, das wäre wohl ein guter Anfang. :-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

sehr viele Infos, schaue ich mir alle nacheinander an. Danke.

>> Um Zeilenumbrüche zu entfernen nutze ich bisher end="" oder .strip(). Es
>> wäre nur gut zu wissen warum da überhaupt Zeilenumbrüche drin sind. Wo
>> kommen die plötzlich her? Ich musste manche zusammengesetzte Strings
>> wieder zerlegen, um zu wissen an welcher Stelle ein .strip() notwendig
>> ist.
>
> Die Builtin-Funktion print() hängt automatisch einen Zeilenumbruch an,
> wenn "end=''" nicht übergeben wird.

Darauf möchte ich gleich eingehen. Hier gibt es ein Missverständnis.
Das print einen Zeilenumbruch anhängt ist ja okay und soll es auch tun. 
Mein Problem ist, dass ich mittendrin im zusammensetzen eines Strings 
plötzlich Zeilenumbrüche in der Ausgabe hatte. Deswegen muss ich während 
des Zusammensetzens diese entfernen. Genauso spielt es verrückt wenn ein 
Komma mit rein kommt für den SCPI Syntax. Jetzt wollte ich die Zeilen 
separieren zum Vorführen und das Problem ist weg. :-( Den .strip() muss 
ich aber im eigentlichen Programm machen, sonst habe ich 2 Zeilen statt 
einer und mit Komma 3 statt einer. Wie gesagt, so wie es jetzt einzeln 
hier steht funktioniert es komischerweise ohne ungewollte 
Zeilenumbrüche.
1
syntaxSCPI = ":TRACe:DATA?"
2
3
def getReadBuffer(start, end, name: str) -> str:    
4
    #scpiCode = (f'{syntaxSCPI} {start}, {end}').strip()
5
    scpiCode = (f'{syntaxSCPI} {start}, {end}, "{name}"')
6
    # Komma nach '{scpiCode}' hier, weil mit/nach '{end},' erneut ein Zeilenumbruch drin wäre
7
    #scpiCode = (f'{scpiCode}, "{name}"') 
8
    return scpiCode 
9
10
temp = getReadBuffer(0, 5, "Buffername")
11
print(temp)
12
print(temp)

von Alexander S. (alesi)


Lesenswert?

Christian M. schrieb:
> Frage eines Anwenders, der vor 20 Jahren das letzte mal im Quelltext
> gewurschtelt hat, was nutzt ihr für Py für eine Umgebung?

Eine Übersicht über Python Umgebungen findest Du z.B. hier

https://www.geeksforgeeks.org/python/top-python-ide/

https://geekflare.com/de/best-python-ide/

https://de.python-3.com/?p=2866

von Norbert (der_norbert)


Lesenswert?

Christian M. schrieb:
> Frage eines Anwenders, der vor 20 Jahren das letzte mal im Quelltext
> gewurschtelt hat, was nutzt ihr für Py für eine Umgebung?

Das ist reine Geschmackssache, je nach persönlicher Präferenz.
Da ist von vi / emacs bis zu Exabyte-großen und oftmals entsprechend 
lahmarschigen IDEs alles möglich. Jedem Tierchen sein Pläsierchen hieß 
es schon damals.

Was ich persönlich allerdings für extrem hilfreich erachte, ist ein 
kleiner lokaler Webserver und die komplette, vollständige Dokumentation 
der genutzten Python Version. Einfacher geht's dann kaum noch.

Für alle installierten/verfügbare Module noch ein:
1
pydoc3 -p8080
 im Terminal geöffnet. Erreichbar dann unter:http://localhost:8080/

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> ähmm. Das ändert alles nichts an den Möglichkeiten der bunten Mischung
> an Zuweisungen, egal welchen Datentyp eine Variable hat(te). Der
> Wildwuchs macht eben Python aus. Mit der nicht vorhandenen
> Datentypsicherheit muss man umgehen lernen, wenn man anderes gewohnt
> ist.

Wildwuchs ist, was Du daraus machst...

Mein Rat wäre, Dich daran nicht allzu sehr aufzuhängen, das ist nur eine 
Gewohnheitssache.

Wenn Du in stark typisierten Sprachen wie Cpp, Java, oder meinetwegen 
auch Golang unterwegs bist, dann denkst Du beim Design und beim Coden 
ständig über Datentypen nach. Denn, na klar: bevor Du eine Variable 
deklarierst, mußt Du natürlich wissen, welchen Typ sie hat.

Daß Python die Datentypen allenfalls als Hinweise (Hints) formuliert, 
heißt aber nicht, daß Datentypen in Python gleichgültig wären oder nicht 
verwendet würden. Sie werden durchaus benutzt und in gewissem Umfang 
auch zur Laufzeit überprüft. Du mußt sie nur nicht zuerst anlegen. Und 
indem ihr Typ nicht an den Variablennamen, sondern an den Variablenwert 
gekoppelt ist, geschieht das erstens automatisch und bedeutet zweitens 
eben auch, daß Du der Variablen -- also: ihrem Namen -- einen anderen 
Wert mit anderem Typ zuweisen könntest. Am Ende habe ich aber im Laufe 
der Jahre nur sehr, sehr wenig Pythoncode gesehen, in dem derselbe 
Variablenname für Werte von unterschiedlichen Typen verwendet worden 
wären. Und auch wenn man das machen kann, rate ich sehr dringend davon 
ab, so etwas zu machen.

von Ein T. (ein_typ)


Lesenswert?

Norbert schrieb:
> def fdivi(a,b):
>     if type(a) is not float or type(b) is not float:
> [/code]

pydoc builtins.isinstance

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> Wie gesagt, so wie es jetzt einzeln
> hier steht funktioniert es komischerweise ohne ungewollte
> Zeilenumbrüche.

Zeig doch bitte mal ein Beispiel, das ungewollte Zeilenumbrüche erzeugt. 
Das Problem ist sicherlich lösbar. :-)

von Norbert (der_norbert)


Lesenswert?

Ein T. schrieb:
> pydoc builtins.isinstance

same dance, another girl

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Norbert schrieb:
> Ein T. schrieb:
>> pydoc builtins.isinstance
>
> same dance, another girl

Sorry, aber das ist "der offensichtlich richtige Weg" versus "geht".

Außerdem funktioniert isinstance() -- im Gegensatz zu Deiner Methode -- 
auch für Kind-Instanzen, was genau das ist, was benötigt, gewünscht, und 
erwartet wird. Bitte beachte die Feinheiten:
1
#!/usr/bin/env python
2
3
class Eins: pass
4
class Zwei(Eins): pass
5
6
if __name__ == '__main__':
7
    z = Zwei()
8
    print(isinstance(z, Eins)) # True # True (1)
9
    print(isinstance(z, Zwei)) # True
10
    print(isinstance(z, (Eins, Zwei))) # True 
11
    
12
    print(type(z) is Eins) # False (2)
13
    print(type(z) is Zwei) # True

(1) Na klar, die Klasse Zwei erbt von der Klasse Eins und ist damit 
auch eine Instanz der Klasse Eins...

(2) ...und das wird hier nicht erkannt.

Meine ganz persönliche Erfahrung ist: wer versucht, klüger als die 
Entwickler einer Sprache zu sein, schießt sich damit meistens in den 
eigenen Fuß. (a)

PEP 20 ("The Zen of Python") sagt: "There should be one-- and preferably 
only one --obvious way to do it". Wenn es für etwas eine 
Builtin-Funktion gibt, ist sie wahrscheinlich dieser "one obvious way".

(a) Gilt vielleicht nicht für PHP... ;-)

von Veit D. (devil-elec)


Lesenswert?

Ein T. schrieb:

> Zeig doch bitte mal ein Beispiel, das ungewollte Zeilenumbrüche erzeugt.
> Das Problem ist sicherlich lösbar. :-)

Ich kann das Problem was es gab nicht mehr nachvollziehen. Hat sich in 
Luft aufgelöst. Der Code erzeugt einen zusammenhängenden String, wie 
gewünscht, auch ohne .strip() und ohne end="". Dann kann ich die 
.strip() und end="" aus dem Modulcode wieder rausnehmen. Ich hatte das 
Modul nochmal umgebaut, am eigentlich verwendeten SCPI Syntax wurde 
jedoch nichts geändert. Vielleicht habe ich doch etwas geändert. Wie 
gesagt, kann es nicht mehr nachvollziehen wie das zu Stande kam.
1
syntax = ":TRACe:DATA?"
2
3
def getReadBuffer(start, end, name: str) -> str:    
4
    scpiCode = (f'{syntax} {start}, {end}, "{name}"')
5
    return scpiCode 
6
           
7
def readBuffer(start, end, name: str = "defbuffer1"):
8
    scpiCode = getReadBuffer(start, end, name)            
9
    print(scpiCode)
10
      
11
#############################################################################
12
    
13
readBuffer(0, 9)
14
readBuffer(0, 9)

von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Ich kann das Problem was es gab nicht mehr nachvollziehen.

Und genau bei so etwas würde git gerne hilfreich zur Seite stehen. ;-)

von Veit D. (devil-elec)


Lesenswert?

mag sein

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> Ich kann das Problem was es gab nicht mehr nachvollziehen. Hat sich in
> Luft aufgelöst. Der Code erzeugt einen zusammenhängenden String, wie
> gewünscht, auch ohne .strip() und ohne end="". Dann kann ich die
> .strip() und end="" aus dem Modulcode wieder rausnehmen. Ich hatte das
> Modul nochmal umgebaut, am eigentlich verwendeten SCPI Syntax wurde
> jedoch nichts geändert. Vielleicht habe ich doch etwas geändert. Wie
> gesagt, kann es nicht mehr nachvollziehen wie das zu Stande kam.

In genau solchen Fällen ist eine Versionskontrolle wie Git wirklich Gold 
wert... also, wenn man sie benutzt. Wirklich, wärmstens empfohlen! :-)

>
1
> syntax = ":TRACe:DATA?"
2
> 
3
> def getReadBuffer(start, end, name: str) -> str:
4
>     scpiCode = (f'{syntax} {start}, {end}, "{name}"')
5
>     return scpiCode
6
> 
7
> def readBuffer(start, end, name: str = "defbuffer1"):
8
>     scpiCode = getReadBuffer(start, end, name)
9
>     print(scpiCode)
10
> 
11
> #############################################################################
12
> 
13
> readBuffer(0, 9)
14
> readBuffer(0, 9)
15
>

Vielleicht so...
1
def read_buffer(start:int , end: int, name: str = "defbuffer1"):
2
    return ':TRACe:DATA? %s, %s, "%s"'%(start, end, name)
3
4
print(read_buffer(0, 9))

...oder so...
1
def read_buffer(start:int , end: int, name: str = "defbuffer1"):
2
    return ':TRACe:DATA? {start}, {end}, "{name}"'.format(
3
        start=start, end=end, name=name)
4
5
print(read_buffer(0, 9))

...?

Für die ganz Harten ginge sogar...
1
def read_buffer(**kwargs):
2
    if 'name' not in kwargs.keys():
3
        kwargs['name'] = 'defbuffer1'
4
    return ':TRACe:DATA? {start}, {end}, "{name}"'.format(**kwargs)
5
6
print(read_buffer(start=0, end=9))

...aber das würde ich an dieser Stelle nicht machen und ist hier nur der 
Vollständigkeit halber gezeigt. :-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke. Es gibt scheinbar unendliche Möglichkeiten des Syntax bzw. 
Formatierungen.
Ich hatte gestern vorm Test auch Python 3.14.0 installiert, vorher war 
es 3.13.3.
Das hier ist demzufolge Python untypischer Syntax? Wenn man etwas 
zusammensetzen möchte. Weswegen es vielleicht Probleme gab.
1
scpiCode = (f'{syntax} {start}, {end}, "{name}"')

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Untypisch daran wären höchstens die Klammern, wofür sollen die gut sein?

Ansonsten sind "f-Strings" mittlerweile sehr Python-typisch.

Aber du kannst auch wie in C++ Strings mit += zusammensetzen.

von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Das hier ist demzufolge Python untypischer Syntax? Wenn man etwas
> zusammensetzen möchte. Weswegen es vielleicht Probleme gab.
> scpiCode = (f'{syntax} {start}, {end}, "{name}"')

Nö. Der ist OK. Es werden zur Laufzeit einfach nur die jeweils aktuellen 
Werte der vier Variablen eingesetzt. Wenn die allerdings – wie evtl. im 
Falle von name – newlines enthalten, werden die natürlich auch in den 
resultierenden String eingefüttert.
1
#!python
2
scpiCode = f'{syntax} {start}, {end}, "{name.strip()}"'
könnte da helfen.
Wobei insgesamt gesehen die Klammern außen herum unnötig sind.

Was und wie man sonst noch alles formatieren kann, ist in der Sektion
›Format Specification Mini-Language‹ in den Python docs gut beschrieben.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

okay, Danke Euch.

von Ein T. (ein_typ)


Lesenswert?

Jörg W. schrieb:
> Untypisch daran wären höchstens die Klammern, wofür sollen die gut sein?

Gute Frage. :-)

> Ansonsten sind "f-Strings" mittlerweile sehr Python-typisch.

Die Begeisterung vieler Python-Entwickler für F-Strings hat sich mir 
bisher allerdings noch nicht erschlossen, zumal ich sie auch schon an 
Stellen sehen mußte, wo sie Injection-Angriffe provoziert haben (think 
Bobby Tables). Aber andererseits ist das vermutlich eine Geschmacks- und 
Gewohnheitsfrage -- und selbst wenn man sie, wie ich, nicht im eigenen 
Code benutzen will, wird man sie früher oder später in fremdem Code 
begegnen und sollte also wissen, daß sie existieren und wie sie 
funktionieren.

> Aber du kannst auch wie in C++ Strings mit += zusammensetzen.

Mitunter auch sehr nützlich sind str.join():
1
#!/usr/bin/env python
2
3
a = list()
4
a.append('eins')
5
a.append('zwei')
6
a.append('drei')
7
print(' '.join(a)) # "eins zwei drei"

und string.Template:
1
#!/usr/bin/env python
2
3
from string import Template
4
t = Template('$eins ist $zwei')
5
print(t.substitute({'eins': 'Das', 'zwei': 'toll'})) # 'Das ist toll'

Die Syntax "${eins}" wird auch unterstützt, wie in verschiedenen Shells.

Wer eine komplette Template-Engine braucht, ist mit Jinja2 prima 
bedient.

von Norbert (der_norbert)


Lesenswert?

Ein T. schrieb:
> Die Begeisterung vieler Python-Entwickler für F-Strings hat sich mir
> bisher allerdings noch nicht erschlossen

Convenience. Man zwingt die Anwender nicht dazu, ständig neuen Versionen 
zu installieren.

f-strings: ab 3.6
template strings: ab 3.14

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich möchte behaupten, dass die F String Methode (und +) der einzige 
Syntax ist, der jedem sofort erschließt was wie zusammengesetzt wird.

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> ich möchte behaupten, dass die F String Methode (und +) der einzige
> Syntax ist, der jedem sofort erschließt was wie zusammengesetzt wird.

Das wirkt zunächst sicherlich so.
Aber es gibt auch gute Gründe so etwas zu machen:
1
#!python
2
fmt = 'Qual:{:+7.3f}   bits:{:032b}   adr:{:08x}'
3
print(fmt.format(2000/3, 123_123, 123_123))
4
# Qual:+666.667   bits:00000000000000011110000011110011   adr:0001e0f3

Vor allem wenn man solch vorbereiteten Formate mehrfach recyceln möchte.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

prinzipiell klar, coole Sache. Mir erschließt jedoch nicht das 123_123 
als Integer durchgeht.

von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Hallo,
>
> prinzipiell klar, coole Sache. Mir erschließt jedoch nicht das 123_123
> als Integer durchgeht.

Gültige und gängige Python Syntax um sehr große Zahlen schnell und 
eindeutig lesbar zu machen ohne mit Finger oder Cursor durch 10,20,30 
Stellen zu rutschen. Arbeite mal des öfteren mit Milliardenwerten ohne 
sci oder eng Formatierung und du wirst es zu schätzen lernen. ;-)
ad1: Geht übrigens auch mit Hex-Werten, zB. ein 32Bit WORD optisch in 
zwei HALFWORDS zu splitten. Oder 32bit Bin-Werte in vier Gruppen à 
8Bits.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

also gültiger Syntax als Trenner. Okay.

Wegen der Methode _getMode().
Abgeschaut und etwas "Cpp struct" eingebaut. :-)
Was haltet ihr davon?
Dann hätte man sprechend mittels "unit" und "command" Zugriff auf die 
Einträge. Wobei ich soeben sehe das noch eine Falle mit _unit vs. unit 
lauern kann.
1
from dataclasses import dataclass
2
from typing import Optional
3
4
@dataclass
5
class Function:
6
    unit: Optional[str]
7
    command: str
8
            
9
class Kscpi:
10
    modes: dict[str, Function] = {
11
        "DCV":      Function("V",   "VOLTage:DC"),
12
        "DCI":      Function("A",   "CURRent:DC"),
13
        "ACV":      Function("V",   "VOLTage:AC"),
14
        "ACI":      Function("A",   "CURRent:AC"),
15
        "2W":       Function("Ω",   "RESistance"),
16
        "4W":       Function("Ω",   "FRESistance"),
17
        "Freq":     Function("Hz",  "FREQuency"),
18
        "Period":   Function(None,  "PERiod"),
19
        "Diode":    Function("V",   "DIODe"),
20
        "Temp":     Function("°C",  "TEMPerature"),
21
        "Cap":      Function("F",   "CAPacitance"),
22
        "Digi V":   Function("V",   "VOLTage"),
23
        "Digi I":   Function("A",   "CURRent")
24
    }
25
26
    def __init__(self):
27
        self._unit = None
28
29
    def get_mode(self, measure: str) -> str:
30
        try:
31
            mode = self.modes[measure]
32
            self._unit = mode.unit
33
            return mode.command
34
        except KeyError:
35
            self._unit = None
36
            return "unknown"
37
38
    def get_unit(self, measure: str) -> Optional[str]:
39
        return self.modes.get(measure, Function(None, "")).unit
40
41
myK = Kscpi()
42
43
print(myK.get_mode("2W"))
44
print(myK.get_unit("DCI"))

: Bearbeitet durch User
von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Abgeschaut und etwas "Cpp struct" eingebaut. :-)

Mein erster Gedanke: Ein guter Programmierer kann ›C-Code‹ in jeder 
beliebigen Programmiersprache schreiben.

Man darf aber dictionaries auch schachteln und sich das Leben einfach 
machen.
1
#!python
2
modes = { # Mit 'ner Liste
3
        'DCV': ['V',   'VOLTage:DC'],
4
        'DCI': ['A',   'CURRent:DC']
5
        }
6
print(modes['DCV'][0], modes['DCV'][1])
7
modes = { # Mit 'nem weiteren dictionary
8
        'DCV': {'unit':'V', 'command':'VOLTage:DC'},
9
        'DCI': {'unit':'A', 'command':'CURRent:DC'}
10
        }
11
print(modes['DCV']['unit'], modes['DCV']['command'])

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ja gut, nur mir wäre das mit 'unit' und 'command' in der Liste zu viel 
Wiederholung, das jedesmal hinschreiben zu müssen. Sieht einfach aus, 
gefallen tut es mir nicht so richtig. Vielleicht auch Geschmackssache?
Nicht böse sein, Zugriff mit Indexnummern geht für gar nicht. :-) Hatte 
schon das Bsp. vom Ein Typ drin. ;-)
Weiter gehts ...

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

nach Überlegung muss ich wieder in alte Muster verfallen. Ich möchte 
nicht immer wieder mittels gewählter Funktion "DVI", "2W" usw. command 
und unit abfragen. Die Messfunktion soll einmal ausgewählt und 
eingestellt werden. Damit müssen alle internen Variablen für diese 
Funktion mit Werten genau dafür zugewiesen werden bzw. sein. Sodass man 
dann nur mittels getUnit o.ä. die Einheit von der aktuellen Einstellung 
abfragt. Sonst stellt jemand DVI ein und fragt dummerweise von 2W ab. 
Das möchte ich nicht.
Ich denke einmal darüber nach ... das war auch der Sinn der 
Function_List.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich glaube jetzt war ich in Gedanken zu schnell. get_mode bleibt intern. 
Wird also nur einmal mit einer set_function Funktion ausgeführt womit 
alles intern neu gesetzt wird.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

so würde ich das aktuell machen wollen. Wäre das Python typisch?
1
from dataclasses import dataclass
2
from typing import Optional
3
            
4
class Kscpi:
5
    
6
    @dataclass
7
    class Function:
8
        unit: Optional[str]
9
        command: str
10
    
11
    modes: dict[str, Function] = {
12
        "DCV":      Function("V",   "VOLTage:DC"),
13
        "DCI":      Function("A",   "CURRent:DC"),
14
        "ACV":      Function("V",   "VOLTage:AC"),
15
        "ACI":      Function("A",   "CURRent:AC"),
16
        "2W":       Function("Ω",   "RESistance"),
17
        "4W":       Function("Ω",   "FRESistance"),
18
        "Freq":     Function("Hz",  "FREQuency"),
19
        "Period":   Function(None,  "PERiod"),
20
        "Diode":    Function("V",   "DIODe"),
21
        "Temp":     Function("°C",  "TEMPerature"),
22
        "Cap":      Function("F",   "CAPacitance"),
23
        "Digi V":   Function("V",   "VOLTage"),
24
        "Digi I":   Function("A",   "CURRent")
25
    }
26
27
    def __init__(self):
28
        self._unit = None
29
        self._command = None
30
31
    def set_function(self, measure: str) -> str:
32
        try:
33
            mode = self.modes[measure]
34
            self._unit = mode.unit
35
            self._command = mode.command
36
            return "ok"
37
        except KeyError:
38
            self._unit = None
39
            self._command = None
40
            return "unknown"
41
    
42
    def get_command(self):
43
        return self._command
44
    
45
    def get_unit(self):
46
        return self._unit
47
    
48
myK = Kscpi()
49
50
print(myK.set_function("2W"))
51
print(myK.get_command())
52
print(myK.get_unit())

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> prinzipiell klar, coole Sache. Mir erschließt jedoch nicht das 123_123
> als Integer durchgeht.

Seit C++14 gibt es das ähnlich in Cpp, nur mit dem "'" anstelle des "_":
1
long long int foo = 1'234'567;

von Ein T. (ein_typ)


Angehängte Dateien:

Lesenswert?

Veit D. schrieb:
> ja gut, nur mir wäre das mit 'unit' und 'command' in der Liste zu viel
> Wiederholung, das jedesmal hinschreiben zu müssen.

Dann schreib das doch nur einmal:
1
#!/usr/bin/env python
2
3
def fm(unit: Optional[str], command: str) -> dict {
4
    return {'unit': unit, 'command': command}
5
}
6
7
modes: dict[str, dict[Optional[str], str]] = {
8
    "DCV": fm("V", "VOLTage:DC"),
9
    # ...
10
}

Aber klar, Du kannst natürlich auch Deine Dataclass benutzen. Das ist 
nicht flashc, es sieht nur ungewöhnlich aus.

Andererseits... Eine Lösung mit Dict-Values in einem Dict hätte wie bei 
meiner Implementierung oben den Vorzug, die Daten einfach in eine Datei 
auslagern zu können, etwa mit YAML. Schau' mal in den Anhang... :-)

Das würde Deine Klasse erstens generischer machen, zweitens die 
Konfiguration ohne Programmierkenntnisse erlauben, drittens könnte die 
Konfigurationsdatei womöglich sogar aus der Gerätedokumentation erzeugt 
werden.

> Nicht böse sein, Zugriff mit Indexnummern geht für gar nicht. :-)

Die kannst Du auch als Enums definieren, dann hast Du was Sprechendes. 
:-)

Edit: Anhang nachgetragen. 8-O

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Ein T. schrieb:
> Veit D. schrieb:
>> Nicht böse sein, Zugriff mit Indexnummern geht für gar nicht. :-)
>
> Die kannst Du auch als Enums definieren, dann hast Du was Sprechendes.
> :-)

Ach so, Nachtrag: mein Beispielcode benutzt gar keine Indexnummern, 
sondern eine Mehrfachzuweisung ([1], erster Codeblock, Zeile 20).

[1] Beitrag "Re: Python SCPI - Keithley DMM6500 u.ä."

von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ein T. schrieb:
> Die Begeisterung vieler Python-Entwickler für F-Strings hat sich mir
> bisher allerdings noch nicht erschlossen, zumal ich sie auch schon an
> Stellen sehen mußte, wo sie Injection-Angriffe provoziert haben

Wenn du sicheren Code schreiben willst, musst du tperl benutzen. :-)

(Die "taint"-Checks von suidperl haben mir schon manche Stellen gezeigt, 
die ich in einem setuid-C-Programm bestimmt übersehen hätte.)

Ein T. schrieb:
> Seit C++14 gibt es das ähnlich in Cpp, nur mit dem "'" anstelle des "_":

Weil der Unterstrich halt ein gültiger Name dort ist, der sehr häufig 
mit dem Präprozessor benutzt wird (beispielsweise zur 
Internationalisierung von Strings). Das wollte man nicht kaputt machen.

Hatte die gleiche Diskussion in der WG14, auch dort hat man sich dann 
für den Apostroph entschieden.

Andere Sprachen (VHDL, Verilog?) hatten den Unterstrich schon länger 
dafür benutzt, weil es natürlich vor allem bei Binärzahlen nötig ist, 
sie irgendwie gruppierbar zu machen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

okay, ich mach das aktuell wie mir gezeigt wurde.  :-)
Danke. Mit _unit und _command habe ich alle Optionen offen zur 
Verwendung.
1
from typing import Optional
2
            
3
class Kscpi:
4
    
5
    def _fm(unit: Optional[str], command: str) -> dict:
6
        return {'unit': unit, 'command': command}  
7
    
8
    modes: dict[str, dict[Optional[str], str]] = {
9
        "DCV":      _fm("V",   "VOLTage:DC"),
10
        "DCI":      _fm("A",   "CURRent:DC"),
11
        "ACV":      _fm("V",   "VOLTage:AC"),
12
        "ACI":      _fm("A",   "CURRent:AC"),
13
        "2W":       _fm("Ω",   "RESistance"),
14
        "4W":       _fm("Ω",   "FRESistance"),
15
        "Freq":     _fm("Hz",  "FREQuency"),
16
        "Period":   _fm(None,  "PERiod"),
17
        "Diode":    _fm("V",   "DIODe"),
18
        "Temp":     _fm("°C",  "TEMPerature"),
19
        "Cap":      _fm("F",   "CAPacitance"),
20
        "Digi V":   _fm("V",   "VOLTage"),
21
        "Digi I":   _fm("A",   "CURRent")
22
    }
23
    
24
    def __init__(self):
25
        self._unit = None
26
        self._command = None
27
28
    def set_function(self, measure: str) -> str:
29
        try:
30
            mode = self.modes[measure]
31
            self._unit = mode["unit"]
32
            self._command = mode["command"]
33
            return "function found"
34
        except KeyError:
35
            self._unit = None
36
            self._command = None
37
            return "unknown"
38
    
39
    def get_command(self):
40
        return self._command
41
    
42
    def get_unit(self):
43
        return self._unit
44
    
45
myK = Kscpi()
46
47
print(myK.set_function("2W"))
48
print(myK.get_command())
49
print(myK.get_unit())

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
>
1
>     def get_command(self):
2
>         return self._command
3
> 
4
>     def get_unit(self):
5
>         return self._unit
6
>

Bis auf die Getter sieht das doch schon wie richtiger Python-Code aus. 
Und was die Getter betrifft... schau mal hier [1] und hier [2] (bei 
RealPython bekomme ich gerade eine Fehlermeldung von Cloudflare, aber 
wenn Du das liest, ist die Seite womöglich schon wieder da). Viel Spaß!

[1] https://docs.python.org/3/library/functions.html#property
[2] https://realpython.com/python-property/

von Ein T. (ein_typ)


Lesenswert?

Jörg W. schrieb:
> Wenn du sicheren Code schreiben willst, musst du tperl benutzen. :-)
>
> (Die "taint"-Checks von suidperl haben mir schon manche Stellen gezeigt,
> die ich in einem setuid-C-Programm bestimmt übersehen hätte.)

Bevor ich Python für mich entdeckt habe, waren Perl4 und Perl5 15 Jahre 
lang meine Go-To-Skriptsprache für alles, das kein C oder C++ gebraucht 
hat. Seit ich allerdings Python habe, nutze ich Perl nurmehr als Filter 
in der Shell, think 'perl -pe'... :-)

> Hatte die gleiche Diskussion in der WG14, auch dort hat man sich dann
> für den Apostroph entschieden.

In der WG14 sitzt Du also auch noch... schläfst Du zwischendurch auch 
mal, oder hast Du den magischen Tag mit 48 Stunden erfunden? 8-O

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Danke. Ich glaube ich habe die @property Verwendung verstanden.
Nur ist das nicht andererseits ein Spiel mit dem Feuer?
Man kann Variablen/Elemente nicht von Methodenaufrufen unterscheiden. 
Irgendwann hat man eine Methode und Element mit gleichen Namen und 
wundert sich.
1
    @property   # Getter - Aufruf ohne ()
2
    def command(self):
3
        return self._command
4
    
5
    @property   # Getter - Aufruf ohne ()
6
    def unit(self):
7
        return self._unit
8
    
9
myK = Kscpi()
10
11
print(myK.set_function("2W"))
12
print(myK.command)
13
print(myK.unit)

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Ein T. schrieb:

> Bevor ich Python für mich entdeckt habe, waren Perl4 und Perl5 15 Jahre
> lang meine Go-To-Skriptsprache für alles, das kein C oder C++ gebraucht
> hat. Seit ich allerdings Python habe, nutze ich Perl nurmehr als Filter
> in der Shell, think 'perl -pe'... :-)

Aber etwas Vergleichbares wie die taint-Checks kenne ich dort nicht. 
suidperl hat mir schon manches Mal geholfen.

Für reine Regexp-Verarbeitung finde ich Perl ansonsten nach wie vor 
angenehmer. $foo =~ re und $x = $1 schreibt sich deutlich handlicher 
als re.match(re, foo) und x = m.group(1).


>> Hatte die gleiche Diskussion in der WG14, auch dort hat man sich dann
>> für den Apostroph entschieden.
>
> In der WG14 sitzt Du also auch noch...

Ich habe die 0b-Zahlen dort mal durchgezogen (nachdem so ziemlich jede 
andere Sprache sie schon hatte :), in dem Zusammenhang kam dann das 
Trennzeichen für die Strukturierung mit hoch.

Ansonsten bin ich da nur noch gelegentlicher interessierter Mitleser. 
Der Datenstrom an Diskussionen ist einfach mal zu viel, um sich das 
alles anzutun.

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> Danke. Ich glaube ich habe die @property Verwendung verstanden.
> Nur ist das nicht andererseits ein Spiel mit dem Feuer?

In der Praxis nicht, allerdings... ich würde die Eigenschaften "command" 
und "unit" ohnehin, wenn schon, als normale Eigenschaften benutzen, also 
ohne properties-Dekorator und ohne "_"-Präfix. Das verstecken zu wollen 
ist der Gedanke eines Entwicklers, der Zugriffsmodifkatoren kennt. Aber 
weil ich ja Deine Vorliebe für Zugriffsmodifikatoren gesehen habe, 
wollte ich Dir einen Weg zeigen, wie Du die Eigenschaften zumindest 
readonly machen kannst. ;-)

Davon abgesehen würde ich selbst "unit" und "command" aber ohnehin 
niemals als Eigenschaften einer Instanz verwenden, sondern nur als 
Rückgabewerte. Wenn Du
Eigenschaften benutzt, mußt Du immer beachten, daß jeder folgende Aufruf 
der get_mode()-Methode diese Eigenschaften verändern könnte. Das wäre 
eine Quelle von schwierig zu findenden, weil nicht offensichtlichen 
Fehlern.

Zudem möchtest Du mit "unit" und "command" ja etwas tun, genauer: das, 
was get_mode() aufruft, will das "command" ans DMM senden und dessen 
Rückgabewert mit der "unit" ausgeben. Was läge da näher, als "unit" und 
"command" einfach an den Aufrufer zu übergeben? Wenn der diese Variablen 
in folgenden Zugriffen überschreibt, dann ist das wesentlich weniger 
fehleranfällig beim Schreiben und sehr viel leichter zu finden, wenn es 
trotzdem geschieht.

Insofern würde ich das zunächst als einfache Funktion gestalten:
1
#!/usr/bin/env python
2
3
def get_mode(measure: str):
4
    modes: dict[str, dict[Optional[str], str]] = {...}
5
    try:
6
        return modes[measure]
7
    except KeyError as e:
8
        raise

Ein weiterer Vorteil ist die Sache mit der Zuständigkeit. Diese Funktion 
ist nur für eine einzige Sache zuständig. Muß das zu Deiner Hauptklasse 
gehören, die die SCPI-Verbindung herstellt, Kommandos darüber sendet und 
die Rückgabe darüber empfängt? Ich finde, nein, das muß es nicht, und 
meiner Meinung nach  sollte es das auch nicht.

Exkurs: Ich würde die Verantwortungen trennen: n Klassen, die 
ausschließlich für die Verbindung, das Senden und Empfangen zuständig 
ist und eine Klasse, die sich nur um Konfigurationsdaten kümmert. Am 
Ende eine Klasse, die diese vorgenannten übergeben bekommt und sie 
benutzt, um die Meßwerte zu erhalten und sie mit den entsprechenden 
Einheiten auszugeben. Die Verbindungsklassen würden dann alle von einer 
abstrakten Basisklasse (BaseConnection) erben und das würde 
sicherstellen, daß sie alle exakt dieselbe API haben... Abstrakte 
Klassen kennst Du ja vermutlich auch schon von Cpp, und in Python geht 
das syntaktisch und technisch etwas anders, aber im Prinzip durchaus 
ähnlich.

Okay, weiter im Text: nun möchte ich das Ding doch mit 
Konfigurationsdateien benuzten -- und bin aber zu faul, meinen ganzen 
Code zu ändern. Enter Magic Methods (auch Dundermethods für "double 
under" genannt):
1
#!/usr/bin/env python
2
import yaml
3
from collections import namedtuple
4
5
UnitCommand = namedtuple('UnitCommand', ['unit', 'command'])
6
7
class GetMode:
8
    def __init__(self, commands: str):
9
        self.commands = commands
10
11
    def __call__(self, measure: str) -> tuple[str]:
12
        try:
13
            return UnitCommand(
14
                self.commands[measure].get('unit'),
15
                self.commands[measure].get('command')
16
            )
17
        except KeyError as e:
18
            raise
19
20
class Keithley:
21
    def __init__(self, model: str):
22
        with open(model, 'r') as ifh:
23
            self.device_config = yaml.safe_load(ifh)
24
        self.get_mode = GetMode(self.device_config['commands'])
25
26
if __name__ == '__main__':
27
    k = Keithley("6550.yml")
28
    
29
    # Rückgabewert verhält sich wie ein Tupel
30
    unit, command = k.get_mode('DCV')
31
    print(unit, command) # "V VOLTage;DC"
32
    
33
    # Rückgabewert verhält sich wie eine Instanz
34
    uc = k.get_mode('DCI')
35
    print(uc.unit, uc.command) # "V VOLTage;DC"

Dank "__call__()" werden die Instanzen von GetMode aufrufbar wie eine 
Funktion oder Methode, "self.get_mode = GetMode(...)" erzeugt eine neue 
Methode in der Keithley-Klasse, und das namedtuple läßt sich wahlweise 
wie ein Tupel oder mit den benannten Feldern wie eine Instanz benutzen. 
Python ist sehr flexibel und bietet viele Möglichkeiten, die andere 
Sprachen nicht kennen. :-)

> Man kann Variablen/Elemente nicht von Methodenaufrufen unterscheiden.

Aber ja doch, Methodenaufrufe enden mit "(...)". :-)

> Irgendwann hat man eine Methode und Element mit gleichen Namen und
> wundert sich.

Die Entwicklung von Software ist nicht trivial, und ein paar Sachen muß 
man sich schon merken... ansonsten kommt beim Testen eine Exception. :-)

von Norbert (der_norbert)


Lesenswert?

Ein T. schrieb:
> ansonsten kommt beim Testen eine Exception.

Eine exzellente Methode um sich eingehend mit der Magie der Exceptions 
zu befassen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

muss ich da erst reindenken wohin das jetzt geht.
Möchte noch eins äußern. Die SCPI Klasse war schon vorher nur für den 
Syntax zuständig. Die hat nichts ans Gerät gesendet oder so. Der 
komplette Syntax muss vorher noch vervollständigt zusammengebaut werden. 
Das ist mein geringstes Problem.
Die get_mode Methode muss den Syntax ändern. Damit legt man die 
Messfunktion fest. Damit werden alle anderen SCPI Syntax Codes die man 
zum messen benötigt automatisch für diese Messfunktion abgeändert. In 
der Function_List gibt es dafür einen Platzhalter "MESSMODE". Der wird 
mit dem rausgefischten Funktionssyntax von get_mode ersetzt. Danach kann 
man alle eingepflegten SCPI Befehle ans Gerät senden wie man lustig ist. 
Das war mein Trick bzw. Idee alle Aufrufe zum messen praktisch zu 
halten. Wenn man sich den Firlefanz von meinem Hauptprogramm wegdenkt, 
waren das nur paar wenige Funktionsaufrufe und man konnte messen. Das 
hat sich nur leider nie jemand so angeschaut.

Ansonsten haste schon recht. Da es in Python keine Kapselung gibt, kann 
man sich ein paar der gedachten privaten _ "Dinge" sparen. Dagegen darf 
von der Function_List nur mit einer Kopie gearbeitet werden. Sonst ist 
der Platzhalter "MESSMODE" zum suchen und ersetzen futsch.

von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Da es in Python keine Kapselung gibt, kann
> man sich ein paar der gedachten privaten _ "Dinge" sparen.

Ich hielt es bis jetzt immer für recht schwer möglich, mit seinen 
unegalen Wurstfingern von außen an Variablen innerhalb einer Klasse 
herum zu spielen, wenn diese self.__xxxxx (double underscore) genannt 
werden.
Die modifiziert man mit einer Methode oder liest sie über @property. 
Aber man manipuliert sie nicht so einfach unkontrolliert von außen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich möchte mich jetzt nicht weiter über das Thema Kapselung C++ vs. 
Python unterhalten. Ich weiß nicht wie das enden würde. Danke für das 
Verständnis. Ich möchte mich in Python reindenken.

Es ging hier und jetzt darum das "Ein T" vorgeschlagen hat, alle 
"privaten" _ Variablen wegzulassen, weil es fast keine gibt. Sie demnach 
sowieso fast alle von außen geändert werden. Bis auf wenige Ausnahmen, 
diese verbleiben mit _ "privat". Mit Doppel _ fang nicht nun nicht an. 
Die allgemeine Konvention ist 1x _.

von Norbert (der_norbert)


Lesenswert?

Veit D. schrieb:
> Ich möchte mich in Python reindenken.

Veit D. schrieb:
> Mit Doppel _ fang nicht nun nicht an.

Vielleicht hilft die Dokumentation dabei, sich vernünftig rein zu 
denken:
9.6. Private Variables

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

dabei geht es um Methoden, nicht um Variablen. Letztere mit 1x _.

von Sheeva P. (sheevaplug)


Lesenswert?

Norbert schrieb:
> Wobei insgesamt gesehen die Klammern außen herum unnötig sind.

Ganz am Rande möchte ich anmerken, daß runde Klammern () einem 
Entwickler in seltenen Momenten ganz schön ins Gesäß beißen können. Denn 
sie erzeugen nach Kontext ein Tupel oder einen Generator:
1
>>> a = ('a', 'b', 'c')
2
>>> print(type(a), a)
3
<class 'tuple'> ('a', 'b', 'c')
4
>>> b = (f'prefix_{item}' for item in a)
5
>>> print(type(b), b)
6
<class 'generator'> <generator object <genexpr> at 0x7c0610551ff0>

von Sheeva P. (sheevaplug)


Lesenswert?

Norbert schrieb:
> Ein T. schrieb:
>> Die Begeisterung vieler Python-Entwickler für F-Strings hat sich mir
>> bisher allerdings noch nicht erschlossen
>
> Convenience. Man zwingt die Anwender nicht dazu, ständig neuen Versionen
> zu installieren.

Man zwingt die Nutzer stattdessen dazu, ständig neue Möglichkeiten für 
die Formatierung von Zeichenketten zu erlernen. Aktuell gibt es dazu 
doch, wenn ich nichts vergessen habe: str.% (str.__mod__), 
string.Template, str.format, f-Strings und neuerdings t-Strings, die im 
Prinzip ja so etwas wie f-Strings auf Steroiden sind. Und dann gibt es 
natürlich Concatenations mit "+". f- und t-Strings finde ich auch nicht 
so richtig elegant, weil sie von den aktuellen globals() und locals() 
abhängen.

Aber diese Nebendiskussion weiterzuführen, wäre dem Thread abträglich. 
Das fände ich bedauerlich, weil Ihr schon so weit gekommen seid.

von Sheeva P. (sheevaplug)


Lesenswert?

Veit D. schrieb:
> Danke. Ich glaube ich habe die @property Verwendung verstanden.
> Nur ist das nicht andererseits ein Spiel mit dem Feuer?

Nicht wirklich, wie bei Variablen gilt die letzte Zuweisung.

> Man kann Variablen/Elemente nicht von Methodenaufrufen unterscheiden.

Wenn aus Dir mal ein fortgeschrittener und erfahrener Python-Entwickler 
geworden ist (was womöglich nicht mehr allzu lange dauern wird, Du 
machst beeindruckend schnelle Fortschritte) und Dir Deskriptoren [1,2] 
oder die magischen Methoden [3,4] begegnen, wirst Du sehen: intern sind 
das alles Aufrufe von Funktionen (oder, na klar, Methoden).

[1] https://docs.python.org/3/howto/descriptor.html
[2] https://realpython.com/python-descriptors/
[3] https://rszalski.github.io/magicmethods/
[4] https://realpython.com/python-magic-methods/

> Irgendwann hat man eine Methode und Element mit gleichen Namen und
> wundert sich.

Wie gesagt, Python ist da leidenschaftslos: die letzte Zuweisung gilt. 
Das ist bei Variablen ja genauso. Warum sollte es bei Eigenschaften 
anders sein?

von Sheeva P. (sheevaplug)


Lesenswert?

Veit D. schrieb:
> Es ging hier und jetzt darum das "Ein T" vorgeschlagen hat, alle
> "privaten" _ Variablen wegzulassen, weil es fast keine gibt.

Wenn ich ihn richtig verstanden habe, schlägt er das nicht deswegen vor, 
weil es keine gibt. Sein Vorschlag ist die beiden Variablen command und 
unit nicht als Eigenschaften, und somit als Zustand, sondern lediglich 
als Rückgabewerte zu verwenden. (Bitte korrigiert mich, wenn ich falsch 
liege.) :)

In Python gibt es außerdem keine privaten oder sonstwie geschützten 
Variablen. Dieses ganze Konzept gehört zu einer kommerziellen 
Entwicklung in der Software als Binärcode ausgeliefert wird. Und nur mit 
den passenden Headern verwendet werden kann, die eine öffentliche (== 
"public") API definieren.

Python ist keine solche Sprache.

> Die allgemeine Konvention ist 1x _.

Die allgemeine Konvention ist 0x _. 1x _ benutzt man nur dann, wenn man 
dem Benutzer seines Codes signalisieren will "hey, fummel da mal nicht 
rum, das wird für interne Sachen benutzt und damit solltest Du echt nur 
rumhantieren, wenn Du wirklich haargenau weißt, was Du da tust und 
warum. Finger weg!".

Diesen ganzen Unfug mit Kapselung und dem Schützen von Variablen sparen 
wir uns in Python einfach mal. Wenn Du nicht merkst, daß Du Deine 
Variablen aus Versehen überschreibst, dann ist Programmieren vielleicht 
nicht Dein Ding.

Das heißt: wir machen keine Unterstriche, weder vor Eigenschaften, noch 
vor Methoden. Alles ist öffentlich ("public"), und wenn Dir mal eine der 
wenigen Ausnahmen begegnet, dann wirst Du es merken. :)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich gebe mir Mühe. Nachdem ich viele kleine Tests gemacht habe muss ich 
das große Ganze neu anschauen. Ich muss überdenken was ich an Aufteilung 
behalte und was nicht. Ob ich im SCPI Modul mehrere Klassen anlege usw. 
Dann können wir erneut darüber reden. Danke @ all.

von Udo K. (udok)


Lesenswert?

Wenn du das ganze linear in C runterprogrammiert hättest, wärest du 
jetzt schon 2x fertig 😉

von Ein T. (ein_typ)


Lesenswert?

Udo K. schrieb:
> Wenn du das ganze linear in C runterprogrammiert hättest, wärest du
> jetzt schon 2x fertig 😉

Mit USB und allem? Du beliebst zu scherzen. Zumal es hier ja auch um das 
Erlernen einer neuen und sehr mächtigen Sprache geht...

von Udo K. (udok)


Lesenswert?

Ein T. schrieb:
> Mit USB und allem? Du beliebst zu scherzen. Zumal es hier ja auch um das
> Erlernen einer neuen und sehr mächtigen Sprache geht...

Das ist einfach in C, Keithley hat dafür fertige Libs.  Ich dachte es 
geht darum Messungen zu automatisieren.  Ob Python mächtig ist?  Ich 
würde sagen, es ist Alleskleber.

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Udo K. schrieb:
> Ein T. schrieb:
>> Mit USB und allem? Du beliebst zu scherzen. Zumal es hier ja auch um das
>> Erlernen einer neuen und sehr mächtigen Sprache geht...
>
> Das ist einfach in C, Keithley hat dafür fertige Libs.

Python auch -- und es kann natürlich auch einfach die Keithley-Libs 
nutzen, denn es wurde ja von Anfang an auch als Glue-Sprache entwickelt.

> Ich dachte es geht darum Messungen zu automatisieren.

Wenn ich das richtig sehe, geht es auch darum, Python zu lernen. Aber 
das weiß unser TO natürlich am Besten, allerdings hat er aber 
ausdrücklich nach Python gefragt -- das steht sogar im Titel dieses 
Threads.

> Ob Python mächtig ist?  Ich
> würde sagen, es ist Alleskleber.

Ja, zu diesem Schluß könnte man kommen, wenn man Python nicht oder nur 
wenig kennt. Die Mächtigkeit erschließt sich erst mit der Erfahrung -- 
allerdings kannst bei Interesse Du diesem Text [1] von Eric S. Raymond 
einen kleinen Vorgeschmack entnehmen. Viel Vergnügen! :-)

Nebenbei bemerkt habe ich Aussagen wie "in der Sprache X wärst Du 
schneller fertig" nicht nur hier im Forum schon tausendmal gelesen und 
gehört, für X als beliebige typsichere, kompilierte Sprache. Leider 
warte ich bis heute darauf, daß sich solche vollmundigen Aussagen auch 
praktisch belegen lassen. Das fände ich wirklich spannend, denn ich 
spreche und benutze nicht nur Skriptsprachen, sondern auch mehrere 
typsichere und kompilierte Sprachen, und halte das aus meinen eigenen 
Erfahrungen heraus für ausgeschlossen. Und alle mir bekannten halbwegs 
seriösen Untersuchungen scheinen meine Meinung zu belegen.

Wenn Du magst, können wir das gerne ausdiskutieren, allerdings schlage 
ich vor, daß Du dann einen eigenen Thread dafür eröffnest. Wir wollen 
diesen Thread hier doch bitte nicht kaputt machen, oder?

[1] https://www.linuxjournal.com/article/3882

von Udo K. (udok)


Lesenswert?

Ein T. schrieb:
>
>> Ob Python mächtig ist?  Ich
>> würde sagen, es ist Alleskleber.
>
> Ja, zu diesem Schluß könnte man kommen, wenn man Python nicht oder nur
> wenig kennt. Die Mächtigkeit erschließt sich erst mit der Erfahrung --
> allerdings kannst bei Interesse Du diesem Text [1] von Eric S. Raymond
> einen kleinen Vorgeschmack entnehmen. Viel Vergnügen! :-)

Ich übersetze Mächtigkeit erstmal mit Kompliziertheit :-)

In dem Problem geht es doch nur um Senden und Empfangen von ein paar 
ASCII Strings.  1-2 Seiten linearer C Code.
Ok, wenn man will, kann man das auch in eine Klasse packen, aber die 
Python Klassen Syntax ist schon ziemlich grauslich.  Da hilft auch kein 
seitenlanger Style Guide.

Aber ich lese hier weiter mit und freue mich über neue Erkenntnisse.

von Norbert (der_norbert)


Lesenswert?

Udo K. schrieb:
> In dem Problem geht es doch nur um Senden und Empfangen von ein paar
> ASCII Strings.  1-2 Seiten linearer C Code.

Es ist nicht bei Strafe verboten, einfach hundert völlig unwartbare 
Einzelfunktionen zu schreiben. Da wünsche ich genauso viel Spaß wie bei 
jeder anderen Programmiersprache.

Udo K. schrieb:
> Ok, wenn man will, kann man das auch in eine Klasse packen, aber die
> Python Klassen Syntax ist schon ziemlich grauslich.

Das mag dem Uninitiierten durchaus so erscheinen.
Man sollte aber das Erlernen einer Programmiersprache nicht dem Benutzen 
einer anderen, bereits erlernten gegenüber stellen.

Wichtig ist, dass die Programmerstellung nicht am Monitor mit bereits 
geöffneten Editor stattfindet, sondern der Denkprozess viel früher auf 
der Couch, im Café oder auf einer Bank am Waldrand beginnt.
Wenn die Struktur klar ist und der Ablauf im Kopf funktioniert, dann 
wird zum ersten Mal ein Computer gebraucht.

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Udo K. schrieb:
> Ich übersetze Mächtigkeit erstmal mit Kompliziertheit :-)

Wenn man komplizierte Probleme löst, kann sogar Python kompliziert 
werden.

> Ok, wenn man will, kann man das auch in eine Klasse packen, aber die
> Python Klassen Syntax ist schon ziemlich grauslich.

Schau, meinetwegen kannst Du gerne gegen Python stänkern, wenn es Dir 
ein unwiderstehliches Bedürfnis ist. Aber kannst Du dafür bitte einen 
eigenen Thread eröffnen, anstatt diesen hier zu beschädigen? Danke.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich würde auch darum bitten das es hier weiter um Python geht und um 
nichts anderes. Wer denkt er kann es in einer anderen Sprache besser und 
schneller der darf das tun, macht dafür einen eigenen Thread auf und 
wartet auf die sachliche Diskussion. Hier bitte nur Python. Danke fürs 
Verständnis.

von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

Das wäre mein aktuelles Grundgerüst. KeithleySCPI.py

Bevor ich weitermache stehe ich vor einem Problem. Parameterfehler 
werden zwar in setFunction() und scpiCommandSyntax() sauber abgefangen, 
dabei haben _unit und _function den Wert None, jedoch führt das zu einem 
Rattenschwanz von Problemen. Wenn man sich vertippt hat, statt "2W" nur 
"W" o.ä. oder später bei den nachgelagerten Messfunktionen, kann ich ja 
nicht in jeder Funktion abfragen ob _function == None ist. Damit 
verschleppe ich nur eine Sache durch den gesamten Code und einen SCPI 
Syntax den ich nie ans Gerät senden möchte. Das gesamte Programm muss 
abbrechen. Ansonsten passiert sowas.
1
function found → 2W
2
RESistance
3
Ω
4
:MEASure:RESistance?
5
function unknown → Tzemp
6
None
7
None
8
:MEASure:RESistance?
"2W" ist korrekt und alles damit wird ausgeführt.
"Tzemp" hat einen Tippfehler, dass wird erkannt und _function bekommt 
None.
_syntaxTable wird nicht aktualisiert, was auch richtig ist.
Bedeutet alle weiter ausgeführten Methoden/Funktionen arbeiten noch mit 
der "2W" Einstellung.
Das kann fatal enden. Es macht für mich keinen Sinn eine 
Sicherheitsabfrage _function == None durch das gesamte Programm zu 
schleppen.
Wäre es korrekt, bei Fehlererkennung mit
except KeyError:
ein
1
sys.exit()
 aufzurufen?

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> Das wäre mein aktuelles Grundgerüst. KeithleySCPI.py

Du machst ja immer noch das Zeug mit den Unterstrichen... :-)

> Bevor ich weitermache stehe ich vor einem Problem. Parameterfehler
> werden zwar in setFunction() und scpiCommandSyntax() sauber abgefangen,
> dabei haben _unit und _function den Wert None, jedoch führt das zu einem
> Rattenschwanz von Problemen. Wenn man sich vertippt hat, statt "2W" nur
> "W" o.ä. oder später bei den nachgelagerten Messfunktionen, kann ich ja
> nicht in jeder Funktion abfragen ob _function == None ist. Damit
> verschleppe ich nur eine Sache durch den gesamten Code und einen SCPI
> Syntax den ich nie ans Gerät senden möchte. Das gesamte Programm muss
> abbrechen. Ansonsten passiert sowas.

Das ist das Verhalten, vor dem jemand weiter oben gewarnt hat: wenn unit 
und command Eigenschaften Deiner Klasse sind, mußt Du die Methoden immer 
in der korrekten Reihenfolge aufrufen, wenn nicht, erhälst Du solche 
Fehler.

Meiner Meinung nach wäre es einfacher, nur eine Tabelle zu benutzen, die 
als Schlüssel den gewünschten Funktionsnamen hat und als Werte jeweils: 
Einheit (unit), Funktion (function), und Syntax (syntax).

Bei den Ersetzungen würde ich im Übrigen
1
tmpl = 'FOO:{messmode}:Bar'
2
tmpl.format(messmode='baz')

oder strings.Template benutzen, um eindeutig zu machen, was da passiert. 
Auf der anderen Seite verstehe ich zwar, daß _updateTable() Dir 
Tipparbeit spart, aber durch die damit verbundene Indirektion wird IMHO 
nicht besonders klar, wie Deine Tabellen am Ende aussehen. ;-)

> Wäre es korrekt, bei Fehlererkennung mit
> except KeyError:
> ein
1
sys.exit()
 aufzurufen?

Das kannst Du machen, aber eleganter wäre es, eine eigene Exception zu 
werfen, mit einer verständlichen Fehlermeldung. Wenn die Exception nicht 
gehändelt wird, beendet sie das Programm.

sys.exit() ist im Übrigen mit Vorsicht zu genießen. Ohne Parameter 
aufgerufen, nimmt der Parameter "status" den Default-Wert None an, und 
das Programm wird mit dem Status 0 beendet. Status 0 bedeutet, die 
Ausführung des Programms war erfolgreich, und das willst Du an dieser 
Stelle vermutlich nicht. Du kannst sys.exit() jedoch mit einem Integer 
aufrufen, der der zurückgegebene Status des Programms wird, alles außer 
der 0 signalisiert einen Fehler. Du kannst sys.exit() aber auch etwas 
mitgeben, das keine Integer ist, dann wird das ausgegeben und das 
Programm mit einem Status ungleich beendet. Kurz gesagt:
1
# Status 0, Programm erfolgreich beendet
2
sys.exit() 
3
sys.exit(0) 
4
5
# Status ungleich 0, Programm hatte Fehler
6
sys.exit(1) 
7
sys.exit(-1)
8
sys.exit(255)
9
10
# Status ungleich 0, Programm hatte Fehler, zudem Ausgabe: "a"
11
sys.exit("a")

von Veit D. (devil-elec)


Lesenswert?

Hallo,

_ ... steht in alle Python Dokus für Privates. :-)

Ih denke es gibt ein Verständnisproblem wie das genutzt werden wird. Mit 
setFunction("x") wird die Messfunktion festgelegt. Widerstandsmessung, 
Spannungsmessung etc., was weiß ich. Alle anderen Funktionsaufrufe 
danach sind in der Reihenfolge egal, es muss nur für den Benutzer Sinn 
machen was er wie misst.
Wenn self._function allerdings den Wert None hat oder anderen Unsinn, 
ist alles danach für den Benutzer sinnlos. Die Abarbeitung muss gestoppt 
werden. Anstatt irgendeinen Fehler mit Abbruch vom Interpreter zu 
erhalten, möchte ich das eleganter beenden.

Okay ich kann demnach folgendes machen.
1
    def setFunction(self, measure: str) -> str:
2
        try:
3
            mode = self._modes[measure]
4
            self._unit = mode["unit"]
5
            self._function = mode["function"]
6
            self._updateTable(self._function)
7
            return f'function found → {measure}'
8
        except KeyError:
9
            self._unit = None
10
            self._function = None
11
            print (f'function unknown → {measure}')
12
            sys.exit("Abbruch")

Was für einen Unterschied macht es ob  sys.exit() erfolgreich oder nicht 
erfolgreich abbricht? Ist doch nur ein Interpreterablaufprogramm.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Hallo,

gut, man kann und sollte noch mit bspw.

finally:
  data.close()

für eine nicht Datenzerstörung etc. sorgen. Wäre auch in meinem 
Interesse, würde die Messdatendatei betreffen. Nur wie sys.exit() 
ausgeht ist laut aktueller Kenntnis doch egal? Ende ist doch Ende. Oder 
nicht? Wem wird es gemeldet oder wer hat etwas davon ob sys.exit() 
erfolgreich oder nicht erfolgreich war?

: Bearbeitet durch User
von Jörg W. (dl8dtl) (Moderator) Benutzerseite


Lesenswert?

Veit D. schrieb:
> Was für einen Unterschied macht es ob  sys.exit() erfolgreich oder nicht
> erfolgreich abbricht? Ist doch nur ein Interpreterablaufprogramm.

Das ist nur in der aufrufenden Umgebung relevant, aber es hat Sinn, es 
sich richtig anzugewöhnen.  Wenn ein Script von dir beispielsweise von 
einem "make" mal aufgerufen wird, dann führt ein exit mit Fehlerstatus 
dazu, dass die Kommandokette an dieser Stelle beendet wird, andernfalls 
läuft sie fröhlich weiter.

Aber statt des try … except … sys.exit siehe oben, gewöhn dich lieber 
dran, deiner Klasse eigene Exceptions beizubringen. Ganz rudimentär 
genügt es für den Anfang auch, sowas zu machen:
1
    def setFunction(self, measure: str) -> str:
2
        try:
3
            mode = self._modes[measure]
4
            self._unit = mode["unit"]
5
            self._function = mode["function"]
6
            self._updateTable(self._function)
7
            return f'function found → {measure}'
8
9
        except KeyError:
10
            raise Exception(f'function unknown → {measure}')

zu schreiben.

Der Vorteil: wenn deine Klasse bspw. von einem GUI-Kontext aus mal 
gerufen werden sollte, zerstört sich nicht gleich die ganze GUI, sofern 
in der GUI pauschal noch ein Exception-Handler installiert ist. Der kann 
dann zumindest mal den String aus deiner Exception in einem GUI-Popup 
zeigen und danach immer noch den geordneten Rückzug antreten (d.h. sein 
"OK"-Button beendet das Programm).

von Veit D. (devil-elec)


Lesenswert?

Hallo,

habe mir das Logging Modul angeschaut. Das dient, was ich lese, dazu 
Fehler des Programmablaufes zu protokolieren mit verschiedenen Levels. 
Das kann ich nicht zum Messdaten schreiben verwenden.
Ich würde aktuell das .json Format (import json) bevorzugen. Wäre das 
eine Basis um später daraus eine Diagramm aus Python heraus erstellen zu 
lassen?

von Veit D. (devil-elec)


Lesenswert?

Jörg W. schrieb:

> Aber statt des try … except … sys.exit siehe oben, gewöhn dich lieber
> dran, deiner Klasse eigene Exceptions beizubringen. Ganz rudimentär
> genügt es für den Anfang auch, sowas zu machen:
> ...

Das macht Sinn. Danke.

von Sheeva P. (sheevaplug)


Lesenswert?

Veit D. schrieb:
> Ih denke es gibt ein Verständnisproblem wie das genutzt werden wird. Mit
> setFunction("x") wird die Messfunktion festgelegt. Widerstandsmessung,
> Spannungsmessung etc., was weiß ich. Alle anderen Funktionsaufrufe
> danach sind in der Reihenfolge egal, es muss nur für den Benutzer Sinn
> machen was er wie misst.

"Alle anderen Funktionsaufrufe" heißt: zuerst muß ein foo() erzeugt 
werden.

Danach (!!1!11!!) muß ein setFunction() (PEP 8: set_function()) 
ausgeführt werden, damit die richtige "Funktion" ausgewählt ist.

Danach (!!1!11!!) fragst Du Eigenschaften des Objekts (der Instanz 
Deiner Klasse) ab (obj.unit und obj.command).

Und danach (!!1!11!!) setzt Du mit scpiCommandSyntax wieder einen 
Zustand Deines Objekts.

Jedes dieser [dD]anach ist vom vorhergehenden abhängig. Mist.

> Wenn self._function allerdings den Wert None hat oder anderen Unsinn,
> ist alles danach für den Benutzer sinnlos.

Es ist möglich ein neues foo() zu erzeugen. Vorhandene zu resetten. Oder 
das ganze Design nicht so, sorry, zu verkacken.

> Die Abarbeitung muss gestoppt werden.

... oder neu gestartet...

> Anstatt irgendeinen Fehler mit Abbruch vom Interpreter zu
> erhalten, möchte ich das eleganter beenden.
1
key: {"syntax": val["syntax"].replace("MESSMODE", identifier)}
2
    for key, val in self._syntaxTable.items()

Einen Python-Entwickler würde ich fragen, ob er noch ganz bei Trost ist.

Hier auch: Du packst Deinen Wert erst mit "{'syntax': syntax}" ein, um 
ihn sofort wieder auszupacken?

von Veit D. (devil-elec)


Lesenswert?

Hallo Sheeva,

Deine jetzige Beschreibung trifft so nicht zu.

> "Alle anderen Funktionsaufrufe" heißt: zuerst muß ein foo() erzeugt werden.
Verstehe nicht was du damit meinst.

Nur mit setFunction() wird die gewünschte Messfunktion festgelegt.

_modes:
ist der Übersetzer von Displayanzeige zu SCPI Syntax der Messfunktion.

_syntaxTable:
ist die jederzeit erweiterbare Auswahl wie gemessen werden soll. Dazu 
muss der Platzhalter "MESSMODE" für die eingestellte Messfunktion 
einmalig in der Tabelle ersetzt werden. Solange bis man eine andere 
Messfunktion mit setFunction() eingestellt hat. Um den Platzhalter zu 
ändern benötige ich _updateTable().

scpiCommandSyntax() gibt nur einen String zurück, es verändert nichts.

_syntaxTable: dict ist das original und bleibt unverändert
_syntax: dict ist die Kopie mit der gearbeitet wird

Das Replacement ist anders wie vorher. Weil jetzt eine Liste mit einem 
Dictionary verschachtelt ist. Ob das einfacher geht weiß ich jetzt 
nicht.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich weiß nicht was daran falsch sein soll. Der Eintrag "syntax" wird aus 
dem Dictionary rausgezogen und ggf. "MESSMODE" ersetzt. Das Ergebnis 
wird ins Dictionary self._syntax geschrieben, damit eine geänderte Kopie 
angelegt. Den Namen self._syntax kann man noch ändern.
1
    def _updateTable (self, replacement: str):   
2
        target: str = "MESSMODE"                
3
        self._syntax = {
4
            key: {"syntax": entry["syntax"].replace(target, replacement)}
5
            for key, entry in self._syntaxTable.items()
6
        }

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Möchte nur sagen, bin am probieren wie es anders geht ...

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> _ ... steht in alle Python Dokus für Privates. :-)

Es gibt in Python keine Privates.

> Ih denke es gibt ein Verständnisproblem wie das genutzt werden wird.

Du denkst an die auszuführenden Schritte und erhälst dann eine API wie:
1
k = KeithleySCPI("Keithley DMM6500")
2
print(k.setFunction("2W"))
3
print(k.function)
4
print(k.unit)
5
print(k.scpiCommandSyntax("measure"))

Ich hingegen denke an eine saubere API und erhalte so etwas wie:
1
k = KeithleySCPI(...)
2
function, unit, value = k.get("2W")

Keine Zustände (außer der Konfiguration), keine Reihenfolge...

> Was für einen Unterschied macht es ob  sys.exit() erfolgreich oder nicht
> erfolgreich abbricht? Ist doch nur ein Interpreterablaufprogramm.

Das ist der Rückgabecode, den die aufrufende Shell erhält. Es ist 
sinnvoll, das korrekt zu machen, also im Erfolgsfall 0 und in allen 
andere Fällen etwas ungleich 0 zurückzugeben. Dadurch kann das Ergebnis 
des letzten Aufrufs in der Shell abgefragt werden (bash: $?) oder es 
können Befehle mit einem logischen UND oder ODER verknüpft werden. Bei 
einem C- oder Cpp-Programm schreibst Du ja am Ende auch ein "return 0" 
hin, nicht nur um die Warnung zu vermeiden, daß main() den Rückgabetyp 
int hat...

von Veit D. (devil-elec)


Lesenswert?

Ein T. schrieb:
> Veit D. schrieb:
>> _ ... steht in alle Python Dokus für Privates. :-)
>
> Es gibt in Python keine Privates.

Ich verstehe euch nicht, weil das regt mich jetzt schon etwas auf. Ihr 
wollt das ich Python konform programmiere. Okay, will ich auch, also 
mache ich das. In allen Python Dokus steht drin, man soll _ verwenden 
für "private" Dinge. Also nicht öffentliche Dinge, bedeutet private 
Dinge. Warum schreibt ihr laufend man soll kein _ verwenden wenn es doch 
in jeder Doku steht auf die ihr selbst immer verweist. Klar ist das nur 
eine Konvention, aber es ist eine dokumentierte Konvention wie man es 
machen soll.

API usw. kommt dran wenn ich Listen gemischt mit Dictionary, Zugriffen 
usw. besser verstanden habe. Da bin ich zu schnell drüber hinweg 
gegangen.

Edit:
API ganz kurz.
> Ich hingegen denke an eine saubere API und erhalte so etwas wie:
[/code]
k = KeithleySCPI(...)
function, unit, value = k.get("2W")
[/code]

k.get("2W") soll nichts messen. Das kann nur die Funktion 
auswählen/einstellen. Es ist damit nicht klar wie überhaupt gemessen 
werden soll. Darüber können wir später noch reden. Eine wichtige Frage 
dazu. Kennst du dich mit dem Messgerät und SCPI Syntax aus? Nicht das 
wir ggf. aneinander vorbeireden. Was wie mit dem Syntax möglich ist, wie 
das funktioniert usw.

: Bearbeitet durch User
von Veit D. (devil-elec)


Angehängte Dateien:

Lesenswert?

Hallo,

neues Grundgerüst vom Modul. Das sollte jetzt Python konform sein.
Die Testaufrufe am Ende gehören wie kommentiert nicht ins Modul. Dient 
nur für den unkomplizierten Test.

Wenn das grundlegend okay, können wir uns über die beste Methode 
unterhalten um dem Messgerät zu sagen wie es messen soll und wie man die 
Werte erhält bzw. in eine Datei schreibt.

Ob man das Keithley SCPI Modul so lässt und alle Funktionen die man mit 
der _syntax_table zur Verfügung hat und dafür Funktionen schreiben muss 
in ein weiteres Modul verlagert. Was ich nicht möchte ist, im 
Hauptprogramm mit den str Namen der _syntax_table operieren. Das sollten 
gleichnamige Funktionen sein die der User verwendet.

: Bearbeitet durch User
von Veit D. (devil-elec)


Lesenswert?

Ein T. schrieb:

>> Ich denke es gibt ein Verständnisproblem wie das genutzt werden wird.
>
> Du denkst an die auszuführenden Schritte und erhälst dann eine API wie:
>
>
1
> k = KeithleySCPI("Keithley DMM6500")
2
> print(k.setFunction("2W"))
3
> print(k.function)
4
> print(k.unit)
5
> print(k.scpiCommandSyntax("measure"))
6
>
>
> Ich hingegen denke an eine saubere API und erhalte so etwas wie:
>
>
1
> k = KeithleySCPI(...)
2
> function, unit, value = k.get("2W")
3
>

Hier gibt es wirklich ein Missverständnis.
1
k.function
2
k.unit
3
k.scpiCommandSyntax("measure")
sind nur für Tests "rausgezogen" wurden. Das bleibt alles intern.
k.get() bleibt auch intern.

Das sind die alten Aufrufer vom Benutzer.
1
changeFunction(keithley, "2W")    # Änderung vorbereiten
2
setFunction(keithley)             # Einstellung übertragen/setzen
3
measureSampling(keithley, 5, 0)   # 5 Einzelmesssungen mit 0sec Pause dazwischen
4
saveBufferWithTimestamp(keithley)

Der Rest wurde/wird intern behandelt.

change und set kann man meinetwegen zusammenfassen zu 
setFunction(keithley, "2W")
1
changeFunction(keithley, "2W")    # Änderung vorbereiten
2
setFunction(keithley)             # Einstellung übertragen/setzen

Wie man dann am Dümmsten die Messwerte abfragt um sie im Terminal 
auszugeben und/oder in eine Datei zu schreiben, dafür bin ich offen.

Das auslesen vom Buffer und speichern in eine Datei hatte bspw. 
saveBufferWithTimestamp(keithley) übernommen.

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> Ein T. schrieb:
>
> Ich verstehe euch nicht, weil das regt mich jetzt schon etwas auf.

Bitte nicht aufregen, wir schaffen das.

> Ihr
> wollt das ich Python konform programmiere. Okay, will ich auch, also
> mache ich das. In allen Python Dokus steht drin, man soll _ verwenden
> für "private" Dinge.

In den mir bekannten Dokumentationen steht, daß das für "internal", 
mithin interne Dinge gedacht sei. Das hat nichts mit "private" oder den 
anderen Zugriffsmodifikatoren aus Cpp, Java und Co. zu tun, aber Du 
versuchst, es genau dafür zu benutzen. PEP 8 [1] sagt dazu:

_single_leading_underscore: weak “internal use” indicator. E.g. from M 
import  does not import objects whose names start with an underscore

Außerdem werden solche Variablen nicht in die Dokumentation (.__doc__) 
der Klasse und ihrer Instanzen aufgenommen. Sie sind also nicht 
"private", sondern nur nicht öffentlich.

Wie schon anfangs erwähnt: Du versuchst, die Dir aus Cpp bereits 
bekannten Zugriffsmodifikatoren in Python nachzuempfinden.

[1] https://peps.python.org/pep-0008/#descriptive-naming-styles

> API usw. kommt dran wenn ich Listen gemischt mit Dictionary, Zugriffen
> usw. besser verstanden habe. Da bin ich zu schnell drüber hinweg
> gegangen.

Das ist allerdings leider ein ziemlich wichtiger Aspekt, den Python auch 
intern fast überall benutzt. Außerdem gibt es noch Tupel, die im Prinzip 
unveränderbare Listen sind, und Sets, um eindeutige Werte zu erzwingen.

> k.get("2W") soll nichts messen. Das kann nur die Funktion
> auswählen/einstellen. Es ist damit nicht klar wie überhaupt gemessen
> werden soll. Darüber können wir später noch reden. Eine wichtige Frage
> dazu. Kennst du dich mit dem Messgerät und SCPI Syntax aus? Nicht das
> wir ggf. aneinander vorbeireden. Was wie mit dem Syntax möglich ist, wie
> das funktioniert usw.

Nein, kenne ich nicht. Bitte korrigiere mich, wenn ich flashc liege, 
aber  normalerweise wird zuerst eine Verbindung zum Gerät hergestellt 
(über USB, TCP, you get the idea), dann werden 1 bis n Kommandos ans 
Gerät gesendet, dessen Antworten auf die Kommandos empfangen, eventuell 
weitere Kommandos gesendet und empfangen, und am Ende wird die 
Verbindung geschlossen.

Ist das ungefähr richtig so?

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> neues Grundgerüst vom Modul. Das sollte jetzt Python konform sein.

Ich verstehe _fs(), _updateTable() und _syntaxTable leider immer noch 
nicht. In der Methode _fs() packst Du den übergebenen Wert zuerst in ein 
Dictionary {'syntax': wert}, nur um ihn in _updateTable() wieder heraus 
zu holen. Dort könntest Du _fs() und dessen Aufrufe einfach weglassen, 
und anstelle von
1
modify = value['syntax'].replace(target, replacement)

einfach
1
modify = value.replace(target, replacement)

benutzen. Das Ein- und Auspacken in das Dictionary ist völlig 
überflüssig -- und eigentlich auch bei _modes weder notwendig, noch 
elegant. (Das hatte ich selbst als nur als Beispiel gebracht, wie man so 
etwas machen könnte, jedoch wäre ein namedtuple hier IMHO trotzdem 
schöner -- wenn man unbedingt will, grundsätzlich würde sogar ein 
einfaches Tupel funktionieren.)

Außerdem kannst Du anstelle Deiner zu ersetzenden Variablen "MESSMODE" 
einen Format-String benutzen:
1
tabelle = {
2
    'averageCount': ':SENSE:{messmode}:AVERage:COUNt ', # korrekt?
3
    'averageRepeat': ':SENSE:{messmode}:AVERage:TCONTrol REPeat'),
4
}
5
for key, value in tabelle.items():
6
    modify = value.format(messmode=replacement)

An einer Stelle ist mir gerade aufgefallen, daß Dein Kommando am Ende 
ein Leerzeichen enthält, das habe ich mit einem Kommentar markiert. Ist 
das gewollt oder womöglich ein Tippfehler?

> Die Testaufrufe am Ende gehören wie kommentiert nicht ins Modul. Dient
> nur für den unkomplizierten Test.

Dafür hat Python einen ganz besonderen Trick:
1
# hier wird alles ausgeführt
2
if __name__ == '__main__':
3
    # das hier nur bei direktem Aufruf ausführen

Alles, was innerhalb der Verzweigung steht, wird nur ausgeführt, wenn 
die Datei als Programm aufgerufen wurde (./datei.py), aber nicht, wenn 
es als Bibliothek geladen wurde (import datei).

von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> Ein T. schrieb:
> Hier gibt es wirklich ein Missverständnis.
>
1
> k.function
2
> k.unit
3
> k.scpiCommandSyntax("measure")
4
>
> sind nur für Tests "rausgezogen" wurden. Das bleibt alles intern.
> k.get() bleibt auch intern.
>
> Das sind die alten Aufrufer vom Benutzer.
>
1
> changeFunction(keithley, "2W")    # Änderung vorbereiten
2
> setFunction(keithley)             # Einstellung übertragen/setzen
3
> measureSampling(keithley, 5, 0)   # 5 Einzelmesssungen mit 0sec Pause 
4
> dazwischen
5
> saveBufferWithTimestamp(keithley)
6
>
>
> Der Rest wurde/wird intern behandelt.

Ah, verstehe. Also könnte man die Schritte "Änderung vorbereiten" und 
"Einstellung übertragen" sowie "5 Einzelmessungen..." und das Speichern 
möglicherweise zu jeweils einem Schritt zusammenfassen?

> Wie man dann am Dümmsten die Messwerte abfragt um sie im Terminal
> auszugeben und/oder in eine Datei zu schreiben, dafür bin ich offen.

Naja, wie und womit möchtest Du die Daten denn weiterverarbeiten? Oder 
möchtest Du sie nur sehen?

> Das auslesen vom Buffer und speichern in eine Datei hatte bspw.
> saveBufferWithTimestamp(keithley) übernommen.

;-)

von Sheeva P. (sheevaplug)


Lesenswert?

Veit D. schrieb:
> habe mir das Logging Modul angeschaut. Das dient, was ich lese, dazu
> Fehler des Programmablaufes zu protokolieren mit verschiedenen Levels.

Das ist die Idee, aber...

> Das kann ich nicht zum Messdaten schreiben verwenden.

... man kann es auch dafür "mißbrauchen".

> Ich würde aktuell das .json Format (import json) bevorzugen. Wäre das
> eine Basis um später daraus eine Diagramm aus Python heraus erstellen zu
> lassen?

Ja, unbedingt -- und Du kannst sogar JSON loggen.

Allerdings ist JSON recht gesprächig, und das erfordert dann natürlich 
einerseits Speicherplatz und andererseits auch ausreichende Kapazitäten 
beim Schreiben und Lesen. Mein Mittel der Wahl sind daher bei so etwas 
die guten alten CSV-Dateien. Dafür besitzt Python ein Standardmodul mit 
einem DictWriter:
1
d = DictWriter(sys.stdout, fieldnames=['a', 'b', 'c'])
2
d.writeheader()
3
d.writerow({'a': 1, 'b': 2, 'c': 3})
4
d.writerow({'a': 1, 'b': 2, 'c': 3})

Im Beispiel schreibe ich einfach auf sys.stdout, aber da kann man 
natürlich auch eine Datei benutzen. Bei sehr großen Datenmengen und 
genügend Zeit zum Komprimieren kann man die Datei auch zum Beispiel mit 
gzip öffnen, dann ist die Datei beim Schreiben gleich auch komprimiert.

von Sheeva P. (sheevaplug)


Lesenswert?

Veit D. schrieb:
> Ein T. schrieb:
>> Veit D. schrieb:
>>> _ ... steht in alle Python Dokus für Privates. :-)
>>
>> Es gibt in Python keine Privates.
>
> Ich verstehe euch nicht, weil das regt mich jetzt schon etwas auf.

Mensch, ärgere Dich nicht. [1]

[1] Beitrag "Hobbybastler gibt sein Projekt Open Source"

> Ihr wollt das ich Python konform programmiere. Okay, will ich auch, also
> mache ich das.

Wir versuchen, Dir Python etwas näher zu bringen.

> In allen Python Dokus steht drin, man soll _ verwenden
> für "private" Dinge. Also nicht öffentliche Dinge, bedeutet private
> Dinge.

Also in den Python-Dokus, die ich gelesen habe wurde meistens versucht 
die Begriffe "private" und "protected" zu vermeiden. Weil sie bei 
Entwicklern aus anderen OO-Sprachen oft zu genau diesen 
Mistverständnissen führen. Und die mir bekannten Python-Dokumentationen 
erwähnen das zwar der Vollständigkeit halber, aber in der Praxis ist der 
Gebrauch eher selten.

> Warum schreibt ihr laufend man soll kein _ verwenden wenn es doch
> in jeder Doku steht auf die ihr selbst immer verweist. Klar ist das nur
> eine Konvention, aber es ist eine dokumentierte Konvention wie man es
> machen soll.

Die Konvention ist: "alles was mit einem Unterstrich beginnt ist mit 
größter Vorsicht zu genießen. Faß das nur dann an wenn Du meinen Code 
verstanden hast und genau weißt was Du tust". Nicht mehr, nicht weniger.

> API usw. kommt dran wenn ich Listen gemischt mit Dictionary, Zugriffen
> usw. besser verstanden habe. Da bin ich zu schnell drüber hinweg
> gegangen.

Das ist in Python aus Sicht von Entwicklern aus klassischen OO-Sprachen 
erst einmal ein Mysterium. Zumal diese Geschichte mit dem "der Datentyp 
hängt am Wert anstatt am Variablennamen" zum Beispiel dazu führt daß in 
Python die Listen und Tupel und sogar die Keys von Dictionaries immer 
Werte verschiedener Typen haben können:
1
a = ["dings", 1, "bums", 2] # list()
2
a = ("dings", 1, "bums", 2) # tuple()
3
a = {"dings": 1, "bums": "zwei", 3: "vier"} # dict()

Gerade das letzte Ding finde ich nach > 10 Jahren Erfahrung vollkommen 
irre und in der Praxis würde ich aus der 3 eine "3" machen.

Auch verschachtelte Datenstrukturen sind kein Thema. Denk an JSON oder 
YAML.

> k.get("2W") soll nichts messen. Das kann nur die Funktion
> auswählen/einstellen.

Die Funktion des Gerätes, nehme ich an?

> Kennst du dich mit dem Messgerät und SCPI Syntax aus?

Das Gerät übersteigt mein Budget leider allzu deutlich und mit SCPI 
kenne ich mich leider auch nicht aus.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich antworte euch später. Muss das sortieren damit ich auch entsprechend 
richtig  antworten kann. Ist ja einiges zusammengekommen von euch.

von Sheeva P. (sheevaplug)


Angehängte Dateien:

Lesenswert?

Veit D. schrieb:
> ich antworte euch später.

Da ich am Freitag auf Geschäftsreise gehe und bis dahin vermutlich mit 
den Vorbereitungen beschäftigt bin, könnte das für mich schwierig 
werden. Aber hier sind viele fachlich sehr gute Leute wie Jörg Wunsch 
oder Ein Typ, die mindestens genauso gut helfen können.

> Ist ja einiges zusammengekommen von euch.

Und um das Zusammengekommene noch ein bisschen größer zu machen, habe 
ich Deine Implementierung aus Deinem Beitrag vom 27.10., 19:43 Uhr, mal 
etwas umgebaut und hier angehängt. Meine großen Änderungen an internen 
Tabellen erkennst Du sicherlich selbst, an anderen Stellen habe ich 
Kommentare mit '###' markiert, damit Du sie leichter finden kannst.

Die Ausgaben Deiner und meiner Varianten sind exakt gleich; meine 
Variante ist wegen der Konfiguration von logging und der eigenen 
Exception mit Kommentar zu "pass" zwar in summa etwas länger, obwohl ich 
Deine Funktionen _fm() und _fs() entfernt und auch sonst ein wenig 
eingespart habe. Oh, und ich sehe gerade daß ich das "from enum import 
Enum" nicht wieder herausgenommen habe, das ist aber überflüssig, weil 
ich in Zeile 88 meines Code eine Mehrfachzuweisung verwendet habe 
anstelle des Zugriffs über Indizes.

Diese ganzen Dictionaries {'syntax': value} habe ich entfernt und 
stattdessen entweder direkt die Werte benutzt, oder für die 
_modes-Tabelle den Datentypus "Tupel". Das ist im Prinzip so etwas wie 
eine Liste, nur unveränderbar:
1
>>> mylist = [1, 2, 3]
2
>>> mylist
3
[1, 2, 3]
4
>>> mylist[1]
5
2
6
>>> mylist[1] = 5
7
>>> mylist
8
[1, 5, 3]
9
>>> mytuple = (1, 2, 3)
10
>>> mytuple
11
(1, 2, 3)
12
>>> mytuple[1] = 5
13
Traceback (most recent call last):
14
  File "<stdin>", line 1, in <module>
15
TypeError: 'tuple' object does not support item assignment
16
>>> mytuple
17
(1, 2, 3)

Auch Methoden wie list.append() etc. kennen Tupel nicht. Daraus ergibt 
sich nebenbei erwähnt auch noch eine andere spannende Eigenschaft: Tupel 
sind im Gegensatz zu Listen und Sets ein "hashable type", und können 
darum auch als Schlüssel eines Dictionary benutzt werden (Fortsetzung):
1
>>> d = {}
2
>>> d[mylist] = 111
3
Traceback (most recent call last):
4
  File "<stdin>", line 1, in <module>
5
TypeError: unhashable type: 'list'
6
>>> d[mytuple] = 111
7
>>> d
8
{(1, 2, 3): 111}

Am Ende habe ich mir noch einmal die Verwendung von Unterstrichen für 
_unit und _function angeschaut und bemerkt: was Du da machst, ist 
absolut korrekt. Ein Benutzer Deiner Bibliothek soll da gar nicht 
manuell dran herum fummeln. Das Zeugs wird nur intern verwendet und 
gesetzt und Markierungen als intern, die die Unterstriche signalisieren, 
sind also absolut richtig. Ebenso ist es sauber, den Zugriff darauf 
"read-only" über Properties zu machen. Jedenfalls so, wie das aktuell 
implementiert ist.

Nichtsdestotrotz solltest Du es damit nicht übertreiben. Ich habe gerade 
mal in die Webframeworks Django und Flask geschaut: in Django sind keine 
5%, und in Flask nicht einmal 3% der Methoden als "internal" markiert. 
Der Erfahrung nach ist es häufig ein Indiz für ein schlechtes Design, 
wenn man solch viele Dinge als internal markieren muß.

Insgesamt habe ich leider auch noch nicht ganz verstanden, wie Dein 
Modul funktioniert. In Deinem Beitrag vom 27.10., 20:05 Uhr, beschreibst 
Du unter "die alten Aufrufe vom Benutzer" eine API, die für mich doch 
sehr nach einer Objektorientierung in C aussieht: der erste Parameter 
ist ein Zeiger auf die Instanz einer struct. So funktionieren 
objektorientierte Sprachen wie C++ und Java übrigens heute noch, auch 
Python. Nur das Python das, was in C++ "this" ist, in seinen Methoden 
explizit mit "self" sichtbar macht.

Nebenbei bemerkt ist "self" nur eine Konvention. Du kannst auch andere 
Namen benutzen, wenn Du von den Usern Deiner Bibliothek gesteinigt 
werden willst. Das hier ist lauffähiger Python-Code:
1
class Ei:
2
    def __init__(ei, eieiei): 
3
        ei.ei = eieiei
4
5
    def printme(hans): 
6
        print(hans.ei)
7
8
if __name__ == '__main__': 
9
    Ei('ach').printme()

Das ist ein Quatsch-Beispiel und würde niemand in der realen Welt so 
machen. Aber es zeigt einerseits wie flexibel und mächtig Python ist und 
andererseits wie sehr die Python-Welt auf Konventionen anstelle von 
Zwang setzt. Und dabei habe ich noch nicht mit interneren Schweinereien 
wie __new__(), Deskriptoren, Speichermanagement oder solchen Dingen 
angefangen. Python ist so einfach wie ein Fahrrad, aber intern mächtiger 
als ein modernes Flugzeug.

Okay, am Ende noch ein paar Fragen: wozu ist _update_table() eigentlich 
da? Dort wird etwas in _syntax_table_modified geschrieben (Glückwunsch 
übrigens dazu, daß Du lange und sprechende Variablennamen benutzt: sehr 
gut!) und es danach in scpi_command_syntax() wieder abgefragt. Könnte 
man diese Methoden nicht womöglich in eine zusammenfassen? Ist das eine 
Besonderheit von SCPI, oder Deines Multimeters?

(Edit: code tags korrigiert - Mod.)

: Bearbeitet durch Moderator
von Veit D. (devil-elec)


Lesenswert?

Hallo,

oh, Danke, ich hatte schon angefangen eine Antwort zu verfassen.
Ich verfasse es neu. :-)  Meine Antwort wegen "_" kann ich rausnehmen. 
:-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

> ... wird zuerst eine Verbindung zum Gerät hergestellt
> (über USB, TCP, you get the idea), dann werden 1 bis n Kommandos ans
> Gerät gesendet, dessen Antworten auf die Kommandos empfangen, eventuell
> weitere Kommandos gesendet und empfangen, und am Ende wird die
> Verbindung geschlossen.
> Ist das ungefähr richtig so?

Ja schon richtig, Funktionen zusammenlegen macht dennoch keinen Sinn. 
Das was ihr in der _syntax_table() seht, ist nur ein Bruchteil aus dem 
SCPI Reference Manual. Mit dem funktionieren erstmal "nur" grundlegende 
Messfunktionen. Alle benötigten Messeinstellungen, die ich oder 
irgendwer für wichtig hält, sollten einzeln zur Verfügung stehen. Damit 
man sich seinen eigenen Messablauf im Hauptprogramm zusammenstellen 
kann. Deswegen kann man keine Funktionen zusammenlegen.

Wegen _fs(), _updateTable() und _syntaxTable und einpacken auspacken. 
Ich dachte das macht eben Sinn, damit es gleich json kompatibel ist mit 
Bezeichner wie 'snytax:''unit:', 'function:' usw.

Das hat sich jetzt sowieso geändert, mit der Codeänderung von Sheeva. 
Auch wenn damit die "Bezeichner" snytax:''unit:', 'function:' für 
spätere json Verarbeitung erstmal wegfallen. Kann man jedoch dann an 
benötigter Stelle einbauen und schleppt es nicht durch den gesamten 
Code. Okay.

Mittels _modes wird der SCPI Syntax intern im Modul vorbereitet für die 
Messfunktion. Spannung, Widerstand, Strom etc. _modes bleibt 
unverändert. Das spiegelt nur die Displaybedienbutton wieder.

'function' ersetzt in der _syntax_table Kopie "MESSMODE". Ab da 
werden/sollen durch glasklare Einzelfunktionen fertig bereitliegende 
SCPI Kommandos an das Keithley gesendet.

Diese Auftrennung finde ich genial, weil das die SCPI Pflege 
erleichtert. Das Keithley SCPI Reference Manual ist mit dem was ihr hier 
seht noch nicht einmal Ansatzweise abgebildet. Das ist der Einstieg mit 
grundlegenden Messfunktionen. Die Auftrennung und Zugriff mittels 
lesbaren Bezeichner möchte ich beibehalten.

Das Leerzeichen muss an manchen SCPI Code am Ende sein, weil da noch ein 
"Integer" angehangen wird beim zusammenbauen. "Integer" für einen Count 
bswp. Das erspart mir an anderen Stellen beim zusammensetzen des SCPI 
Syntax Ärger. Der SCPI Syntax ist auch an manchen Stellen nicht 100% 
logisch, da muss gewisse Dinge lieber vorher anpassen und erspart sich 
Nacharbeit.

> changeFunction(keithley, "2W")    # Änderung vorbereiten
> setFunction(keithley)             # Einstellung übertragen/setzen
> measureSampling(keithley, 5, 0)   # 5 Einzelmesssungen mit 0sec Pause
> dazwischen
> saveBufferWithTimestamp(keithley)
>
>
> Der Rest wurde/wird intern behandelt.

> Ah, verstehe. Also könnte man die Schritte "Änderung vorbereiten" und
> "Einstellung übertragen" sowie "5 Einzelmessungen..." und das Speichern
> möglicherweise zu jeweils einem Schritt zusammenfassen?

Nein, möchte ich nicht zusammenfassen. changeFunction und setFunction 
kann man meinetwegen zusammenfassen. Den Rest auf keinen Fall. Das 
Keithley hat gefühlt tausende Möglichkeiten wie es messen soll. Das kann 
man nicht in eine Funktion werfen. Ich möchte auch nicht jedesmal die 
Funktion als Parameter mitgeben. Einmal ausgewählt soll es bei der 
Funktion Spannung oder Widerstand usw. messen bleiben bis zur gewollten 
Änderung. Ansonsten hat man viel zu viele Parameter die man einer 
Funktion mitgeben muss. Das würde im Kaos enden. Viele klare 
Einzelfunktionen haben auch den Vorteil in der Bedienung, entspricht dem 
was man auf dem Display drücken könnte und könnte später einer GUI 
Bedienung dienlich sein. GUI ist aber in ferner Zukunft.
Lieber glasklare Einzelfunktionen anstatt zu viele Parameter einer 
Multikultifunktion. Das erspart auch das ständige auswerten der 
eigentlichen Messfunktion (Spannung, Widerstand, Strom) die ja sowieso 
gleich bleibt bis zur expliziten Änderung.

Datenverarbeitung.
Alles im Terminal ausgeben ist gut. Zusätzlich in einer Datei speichern 
noch besser. Ob ich die Auswertung in Excel mache oder nicht ist davon 
erstmal unabhängig.

Wegen .json speichern und loggen etc.
Sollte auf meinen recht modernen Rechner alles kein Problem sein. 
Rechenleistung und Speicher satt vorhanden. Außerdem ist das kein 
Messplatz in einer Firma sondern zu Hause Hobby. Ich messe nicht jede 
Woche irgendwas. Das Keithley konnte ich hier im Forum günstig erwerben, 
nur deswegen ist sowas in meinem Besitz.

> Okay, am Ende noch ein paar Fragen: wozu ist _update_table() eigentlich
> da? Dort wird etwas in _syntax_table_modified geschrieben (Glückwunsch
> übrigens dazu, daß Du lange und sprechende Variablennamen benutzt: sehr
> gut!) und es danach in scpi_command_syntax() wieder abgefragt. Könnte
> man diese Methoden nicht womöglich in eine zusammenfassen? Ist das eine
> Besonderheit von SCPI, oder Deines Multimeters?

Der Sinn ist, dass ich ein in den meisten Fällen fertig modifiziertes 
SCPI Kommando bereitliegen habe und nur abrufe, um es dann zu senden.

aus dem Hauptprogramm:
1
measureSampling(keithley, 10, 1) # 10 Einzelmessungen, 1s Pause dazwischen
2
measureOnce(keithley)            # Einzelmessung

Mit measureOnce(keithley) bspw. hole ich aus dem Modul den in der 
Tabellenkopie bereitliegenden Syntax für das SCPI Kommando.
1
scpiCode: str = device.scpi.getMeasureOnce()
ruft auf:
1
def getMeasureOnce (self) -> str:  
2
    return self._getSCPIcommandByName("measure")
zeigt auf Eintrag:
1
_FunctionItem("measure", ":MEASure:MESSMODE?"),

welcher dann bspw. ":MEASure:VOLTage:DC?" lautet in der Tabellenkopie.

Das bereitlegen und nur abrufen spart Zeit. Jetzt habe ich darüber 
nachgedacht, also wie immer ;-), die Gedanken dazu kommen natürlich aus 
meiner kleinen AVR Welt. Womit man den vorhandenen Speicher nutzen 
sollte um dafür Rechenzeit einzusparen. Gut, ein PC hat genügend 
Rechenleistung um paar zusätzliche Methodenaufrufe zu erledigen, auch 
wenn diese immer wieder gleich sind und überflüssig wären. Das sollte 
nicht stören. Das würde bedeuten die Tabellenkopie fliegt raus und jeder 
Funktionsaufruf wie bspw.
1
measureOnce(keithley)
welches
1
def getMeasureOnce (self) -> str:  
2
    return self._getSCPIcommandByName("measure")
aufruft, modifiziert {messmode} live vor der Rückgabe.
Das müsste man dann in jede Methode schreiben, was ich mir wie erwähnt 
ersparen wollte, den immer gleichen Aufruf in allen Methoden. Wenn ich 
so darüber nachdenke, also wie immer ;-), finde ich die modifizierte 
Tabellenkopie nicht schlecht. ;-)

Wenn der Ablauf noch nicht verständlich ist, dann belassen wir es 
erstmal dabei und reden nur darüber ob Tabellenkopie oder live 
Modifizierung sinnvoll ist. Dann baue ich alles um.

von Ein T. (ein_typ)


Angehängte Dateien:

Lesenswert?

Veit D. schrieb:
> Das hat sich jetzt sowieso geändert, mit der Codeänderung von Sheeva.
> Auch wenn damit die "Bezeichner" snytax:''unit:', 'function:' für
> spätere json Verarbeitung erstmal wegfallen. Kann man jedoch dann an
> benötigter Stelle einbauen

Puh, das war jetzt ein recht langer Beitrag von Dir, den ich mir morgen 
und am Montag noch einmal gepflegt zu Gemüte führen möchte. Aber wenn 
ich mir so die Ersetzungen anschaue, die Dir vorschweben... da kann man 
ja die Möglichkeiten, die Python zum Ersetzen / Einsetzen von 
Zeichenketten bietet, recht geschickt miteinander kombinieren. Schau mal 
in den Anhang: da wird zunächst der {modus} per str.format ersetzt, dann 
ein string.Template aus dem Ergebnis gemacht, die gegebenen Parameter 
und mit jenen dieses Template abgeglichen... und alles mit einer 
sauberen Fehlerbehandlung durch eigene Exceptions. Ist ein Spielbeispiel 
und vielleicht noch nicht bis ins letzte Detail durchdacht, aber es 
zeigt den Weg, wie das mit mehreren Ersetzungen und Prüfung der 
Parameter ginge...

Besonders elegant finde ich dabei, daß sich die Parameter (im Beispiel a 
und b) mit string.Template.get_identifiers() extrahieren und gegen die 
dynamischen Keyword-Arguments von fill() prüfen lassen. :-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

Template Ersetzung, auch interessant. Sind bald zu viele Möglichkeiten. 
:-) Danke.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

also wenn ich
1
_syntax_table: dict = {
2
        "measure": ":MEASure:{messmode}?"
durch das
1
_syntax_table: dict = {
2
        "measure": ":MEASure:$messmode?"
ersetze, kann ich damit folgendes machen  :-)  :-)
1
from string import Template
2
3
t = Template(k.scpi_command_syntax("measure"))
4
result = t.substitute(messmode=k.function)
5
6
print(result)
müsste ich dann in allen weiteren Methoden aufrufen, damit ich live den 
Rückgabe String modifiziere und damit ohne Tabellenkopie auskomme. Ich 
denke mit Einzeltests habe ich genug gemacht, es wird komplett umgebaut, 
dann sieht man das große Ganze besser.

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

Veit D. schrieb:
> ersetze, kann ich damit folgendes machen  :-)  :-)

Klar, das kannst Du so machen:

>
1
> from string import Template
2
> 
3
> t = Template(k.scpi_command_syntax("measure"))
4
> result = t.substitute(messmode=k.function)
5
> print(result)
6
>
> müsste ich dann in allen weiteren Methoden aufrufen, damit ich live den
> Rückgabe String modifiziere und damit ohne Tabellenkopie auskomme. Ich
> denke mit Einzeltests habe ich genug gemacht, es wird komplett umgebaut,
> dann sieht man das große Ganze besser.

Ja, natürlich. Du kannst Deine Ersetzungen aber auch im Konstruktor 
machen und dadurch eine große Tabelle mit allen Messmodi erzeugen, wenn 
Du magst...

Meine Idee bezog sich aber auf den netten Nebeneffekt von 
strings.Template, daß man dort mit der Methode get_identifiers() die vom 
Template erwarteten Variablennamen herausziehen kann, sowie auf die 
Verarbytung von dynamischen Keyword-Argumenten ("**kwargs", wobei der 
Bezeichner "kwargs" nur Konvention ist, auch "**kwds" sieht man hie und 
da), die sich dann gegen die erwarteten Namen des Templates überprüfen 
lassen. Denn im Endeffekt möchtest Du ja zwei Ersetzungen: einmal die 
von "measure", und dann jene eventueller Parameter, die diese Messung 
erwartet. Fangen wir mit *args und **kwargs an:
1
#!/usr/bin/env python
2
3
def dynamic_lists(*args):
4
    print(args.__class__.__name__, args)
5
6
def dynamic_keywords(**kwargs):
7
    print(kwargs.__class__.__name__, kwargs)
8
9
if __name__ == '__main__':
10
    a = [1, 2, 3]
11
    dynamic_lists(a)                # tuple ([1, 2, 3],)
12
    dynamic_lists(*a)               # tuple (1, 2, 3)
13
    dynamic_lists(1, 2, 3)          # tuple (1, 2, 3)
14
15
    b = {'a': 1, 'b': 2, 'c': 3}
16
    dynamic_keywords(**b)           # dict {'a': 1, 'b': 2, 'c': 3}
17
    dynamic_keywords(a=1, b=2, c=3) # dict {'a': 1, 'b': 2, 'c': 3}

Du siehst: *args bezeichnet eine variable Liste von Argumenten (okay, in 
der Funktion ist es dann ein Tupel, also eine unveränderliche Liste), 
sowas kennt man in anderen Sprachen als "variadische Argumente" oder 
"varargs". **kwargs erweitert dieses Konzept auf Schlüsselwort-Argumente 
(keyword arguments), die mit "name=wert" übergeben werden.

Durch die Kombination dieser Features, string.Template.get_identifiers() 
und  **kwargs, lassen sich die der Funktion übergebenen 
Schlüsselwort-Argumente gegen die vom string.Template erwarteten 
Bezeichner abgleichen. Natürlich ist es aber auch möglich, das mehrfach 
zu machen (allerdings wird das ab einer gewissen Anzahl dann etwas 
unübersichtlich mit dem $-Zeichen:
1
#!/usr/bin/env python
2
from string import Template
3
4
if __name__ == '__main__':
5
    s = 'eins:$measure:zwei $$a $$b'
6
    tpl1 = Template(s)
7
    res1 = tpl1.substitute(measure='DINGS')
8
    tpl2 = Template(res1)
9
    res2 = tpl2.substitute(a=1, b=2)
10
    print(res1) # eins:DINGS:zwei $a $b
11
    print(res2) # eins:DINGS:zwei 1 2

Übrigens, wie oben schon erwähnt: wie in einer Shell läßt sich eine 
Variable wie $measure auch als ${measure} schreiben. Das ist wichtig, 
wenn nach dem Variablennamen eine Zeichenkette stehen soll, die 
ansonsten selbst Teil des Variablennamens sein könnte:
1
#!/usr/bin/env python
2
from string import Template
3
4
if __name__ == '__main__':
5
    s1 = 'eins$measurezwei'
6
    print(Template(s1).get_identifiers()) # ['measurezwei']
7
    s2 = 'eins${measure}zwei'
8
    print(Template(s2).get_identifiers()) # ['measure']

Und um ein Dollarzeichen zurückzuerhalten kann einfach $$ benutzt 
werden, das verwende ich im zweiten Beispiel dieses Beispiel für die 
Variablen $a und $b in der zweiten Stufe.

HTH und viel Vergnügen! :-)

von Veit D. (devil-elec)


Lesenswert?

Hallo,

vielen Dank an alle für die vielen Informationen, Erklärungen, Code 
usw.. Ich muss jetzt jedoch erstmal die Bremse reinhauen. Leider. Die 
Informationsflut ist doch schon ganz schön hoch. Ich ändere mein 
Programm nach besten neuen Wissen.

Ich möchte zusätzlich bis Weihnachten noch einen Umbau an der 
Modelleisenbahn machen, inkl. C++ Programmänderung. Sodass ich langsam 
in zeitliches Gedrängel gerate.

Wenn ich den Pythoncode für mich zufriedenstellend geändert habe, melde 
ich mich wieder. Messdaten speichern ist ja auch noch Thema. Ich will 
das jetzt ungern abwürgen, muss es jedoch irgendwie tun. Bitte versteht 
mich. Ich bleib ja dran. Ist nur verschoben. Vielleicht finde ich auch 
zwischendurch dafür Zeit. Aufgeschoben ist nicht aufgehoben. :-)

Wir hören uns!

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.