Forum: PC-Programmierung Konfiguration für ein Python Package speichern


von Phil (Gast)


Lesenswert?

Hallo zusammen,

ich bastel aktuell an einem Python Package um diverse Messgeräte zu 
steuern. Solange das ganze nur für mich war, habe ich einfach so etwas 
wie die IP Adresse der Ethernet zu GPIB Bridge als default Parameter im 
Konstruktor gesetzte. Aber nun benutzen auch andere das Package in 
leicht anderen Setups.

Daher die Frage: Wie speichert man am elegantesten Konfigurationsdaten, 
so dass das Package sie von überall aus finden kann.

Sehr oft teste ich nur mal eben schnell etwas in der Konsole und da 
würde ich es gerne vermeiden jedes Mal IP Adressen usw. eintippen zu 
müssen.

Ich hätte es gerne z.B. so:

import package

instrument = package.HP34401A(gpib=4)
instrument.get_reading()

Die IP Adresse des Ethernetadapters soll irgendwo gespeichert sein, wo 
das package ran kommt. Ändern würde ich sie dann gerne über Methoden des 
Packages

z.B.
package.set_bridge_ip("192.168.0.7")

Gibt es hierfür einen konventionellen Weg? Sicher gibt es unter Windows 
und Linux irgendwelche Ordner die immer da sind und in die man eine 
config Datei legen könnte, aber vielleicht weiß ja jemand, wie man es 
"richtig" macht.

Vielen Dank und viele Grüße
Philipp

von Imonbln (Gast)


Lesenswert?

Phil schrieb:
> Gibt es hierfür einen konventionellen Weg? Sicher gibt es unter Windows
> und Linux irgendwelche Ordner die immer da sind und in die man eine
> config Datei legen könnte, aber vielleicht weiß ja jemand, wie man es
> "richtig" macht.

Alle Wege führen nach Rom, von daher keine Ahnung, ob dieser Weg der 
richtige ist. Aber ich
würde der Klasse HP34401A eine classmethode spendieren, welche die 
Klasse aus einer Konfiguration initialisiert. OB die Konfiguration dann 
in toml, yaml, json oder was anders ist spielt keine Rolle.

Dann kann dein Programm die üblichen verdächtigen (Ordner) abklappern 
und wenn dort eine Konfiguration liegt wird die Klasse damit erzeugt.

ungefähr so:
1
class HP34401A:
2
3
   def __init__(self, ip='127.0.0.1', gpib=4):
4
       self.ip = io
5
       self.gpib= gpib
6
7
   [...]
8
9
   @classmethod
10
   def from_json(cls, filename):
11
       with open(filename) as json_file:
12
            data = json.load(json_file)
13
       return cls(data['ip'], data['gpid'])
14
15
[...]
16
17
instrument = package.HP34401A.from_json('myconfig.json')

von Nico T. (wurstnase)


Lesenswert?

Appdirs und configparser z.B.

von Sheeva P. (sheevaplug)


Lesenswert?

Imonbln schrieb:
> würde der Klasse HP34401A eine classmethode spendieren, welche die
> Klasse aus einer Konfiguration initialisiert. OB die Konfiguration dann
> in toml, yaml, json oder was anders ist spielt keine Rolle.
> [...]
> ungefähr so:
>
>
1
> 
2
> class HP34401A:
3
>    [...]
4
> 
5
>    @classmethod
6
>    def from_json(cls, filename):
7
>        [...]
8
> 
9
> instrument = package.HP34401A.from_json('myconfig.json')
10
> 
11
>

Das wäre ein Ansatz, würde allerdings erfordern, daß Du für jede 
mögliche Methode zur Konfiguration eine eigene Klassenmethode schreiben 
mußt, was -- je nachdem, was Du alles anbieten möchtest, einerseits 
einen recht hohen Aufwand mit viel Boilerplate bedeuten kann und 
andererseits provoziert, daß wohlmeinende Anwender "freundlich anregen", 
Du mögest doch bitte außerdem noch ihre tolle und völlig unverzichtbare 
Möglichkeit anbieten. ;-)

Möglicherweise könnte es daher einfacher sein, wenn der Konstruktor 
einfach ein Dictionary (dict()) mit den Konfigurationsdaten übernimmt, 
dieses auf Fehler und Vollständigkeit überprüft und diese Daten benutzt. 
Dann spielt es keine Rolle mehr, woher die Daten kommen: aus einem 
csv.DictReader, einer .ini-Datei die mit configparser gelesen und 
umgewandelt wurde, aus einem DictCursor einer Datenbank, einer toml / 
yaml / json-Datei, oder womöglich aus einer der einfachsten aller 
Konfigurationsdateiformate in Python, nämlich: Python-Code. ;-)

Lustigerweise bietet Python für solche Dinge eine weitere hübsche 
Möglichkeit, nämlich die **kwargs, also beliebige Keyword-Argumente 
übergeben zu können:
1
#!/usr/bin/env python
2
3
class HP34401A:
4
    def __init__(self, **kwargs):
5
        self.ip = kwargs.pop('ip')
6
        self.gpib = kwargs.pop('gpib')
7
        self.klaus = kwargs.get('klaus', 'Dieter') # yay!
8
9
    def __repr__(self):
10
        return '<{}(ip={!r}, gpib={}>'.format(
11
            self.__class__.__name__, self.ip, self.gpib)
12
13
14
if __name__ == '__main__':
15
    import sys
16
    import json
17
18
    try:
19
        print( HP34401A(ip='192.168.0.1', gpib='4') )
20
    except Exception as e:
21
        print(type(e).__name__, str(e), sep=':', file=sys.stderr)
22
23
    try:
24
        print( HP34401A(ip='192.168.0.2', gpib='4', klaus='Hans') )
25
    except Exception as e:
26
        print(type(e).__name__, str(e), sep=':', file=sys.stderr)
27
28
    try:
29
        text = '{"ip": "192.168.0.3", "gpib": 6}'
30
        js = json.loads(text)
31
        print( HP34401A(**js) )
32
    except Exception as e:
33
        print(type(e).__name__, str(e), sep=':', file=sys.stderr)
34
    
35
    try:
36
        text = '{"ip": "192.168.0.4"}'
37
        js = json.loads(text)
38
        print( HP34401A(**js) ) # Syntax!
39
    except Exception as e:
40
        print(type(e).__name__, str(e), sep=':', file=sys.stderr)

Der geneigte Leser möge beachten, wie ich im Initializer der Klasse für 
Parameter, die Methode "pop()" und für optionale Parameter die Methode 
"get()" benutze, siehe Kommentar "yay". Außerdem muß das 
Konfigurationsdictionary zum Aufruf der Klasse mit '**' übergeben 
werden, siehe Kommentar "Syntax!".

von Imonbln (Gast)


Lesenswert?

Sheeva P. schrieb:
> Der geneigte Leser möge beachten, wie ich im Initializer der Klasse für
> Parameter, die Methode "pop()" und für optionale Parameter die Methode

Dein Vorschlag hat aber den Nachteil, dass er die Information welche 
Parameter die Klasse braucht hinter den Ominösen **kwargs, versteckt.
Schön das eine Klasse dann ein Key-Error beim Erzeugen wirft, wenn Sie 
nicht alle Parameter bekommt, welche sie gerne hätte, durch den pop 
Einsatz. Aber sauberes Design (was du meisten mit Leidenschaft 
vertrittst) sieht in diesen Fall anders aus.

Sicher es gibt fälle in welchen **kwargs eine gute Option ist 
(Datenbanken, Protokoll Header, etc) aber dieser ist keiner davon und 
der Nachteil das man nur durch Codelesen (oder Dokumentation, wenn 
Vorhanden) herausbekommt was der Konstruktor wirklich will widerspricht 
auch den Zen of Python in mindestens folgenden Punkten:

* Beautiful is better than ugly.
* Explicit is better than implicit.
* Readability counts.

Abgesehen davon kannst du die Klasse auch ganz klassisch so schrieben 
Deine Tricks gehen dennoch:
1
class HP34401A:
2
   def __init__(self, ip, gpib,  klaus='Dieter'):
3
        self.ip = ip
4
        self.gpib = gpib
5
        self.klaus = klaus
6
   
7
   def __repr__(self):
8
        return '<{}(ip={!r}, gpib={}>'.format(
9
            self.__class__.__name__, self.ip, self.gpib)
10
11
12
if __name__ == '__main__':
13
   import json
14
   import sys
15
16
   print( HP34401A(ip='192.168.0.1', gpib='4') )
17
   print( HP34401A(ip='192.168.0.2', gpib='4', klaus='Hans'))
18
   
19
   try:
20
      text = '{"ip": "192.168.0.4"}'
21
      js = json.loads(text)
22
      print( HP34401A(**js) )
23
   except TypeError as e:
24
      print(type(e).__name__, str(e), sep=':', file=sys.stderr)

Das hat zudem denn Vorteil, das deine Ide oder das Builtin help von 
Python, dir denn richtigen Prototypen der Klasse sagen kann.

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.