Seite 1 von 1
[Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 14:54
von Proxy87
Hallo zusammen,
ich habe ein Problem mit Klassen, ich versuche mein Problem zu beschreiben und vielleicht versteh ich nur die Grundlage nicht genau.
Pixend.py:
Ich habe eine Klasse erstellt.
Code: Alles auswählen
class plc():
"""
Nur zum kommunizieren mit der Pixtend PLC
"""
def __init__(self):
if start.DEBUG_XML == 'False':
from pixtendlib import Pixtend
self.p = Pixtend() # Create instance
else:
print('Pixend.py ==> Pixtend im Modus Debug: ' + str(start.DEBUG_XML))
self.p = None # Create instance
# Open SPI bus for communication
if start.DEBUG_XML == 'False':
try:
self.p.open()
except IOError as io_err:
# On error, print an error text and delete the Pixtend instance.
print("Error opening the SPI bus! Error is: ", io_err)
self.p.close()
self.p = None
def run(self):
....................................
Habe noch ein Objekt erstellt: pixboard = plc()
Threading.py:
Threading Klasse:
Code: Alles auswählen
class Pixtend_Thread(threading.Thread):
def __init__(self, threadID, name, delay, Startup, Debug):
threading.Thread.__init__(self)
import Pixtend
self.threadID = threadID
self.name = name
self.delay = delay
self.startup = Startup
self.Debug = Debug
def run(self):
if start.startup_done == True:
while True:
time.sleep(int(self.delay))
tstart = time.time()
if self.Debug == 'True': #Todo korrekte bedingung?
Pixend.pixboard.run()
else:
print ('Threading_PRG.py/Pixtend im Debug Modus')
tstop = time.time()
print("Threading_PRG.py/Zeit für PixendThread - " +str(tstop - tstart))
Jetzt kommt leider jedes mal ein Print out "Pixend.py ==> Pixtend im Modus Debug:"
habe versucht das zu ändern indem ich das Objekt im der Hauptklasse zu initiieren in dem __init__ Block.
Und zwar so:
Code: Alles auswählen
# --- Steuerung der SPS aus dem Programm ---
class Pixtend_Thread(threading.Thread):
pixboard = Pixtend.plc()
def __init__(self, threadID, name, delay, Startup, Debug):
threading.Thread.__init__(self)
import Pixtend
self.threadID = threadID
self.name = name
self.delay = delay
self.startup = Startup
self.Debug = Debug
def run(self):
if start.startup_done == True:
while True:
time.sleep(int(self.delay))
tstart = time.time()
if self.Debug == 'True': #Todo korrekte bedingung?
self.pixboard.run()
#Pixend.self.pixboard.run()
Wenn ich jedoch das Thread Objekt erzeuge wird leider "pixboard" mit erstellt und es gibt die Ausgabe. Jetzt möchte ich gerne wenn ich den Aufruf auskommentiere, dass alles clean bleibt und nicht ein Teil davon doch ausgeführt wird.
#Threading_PRG.thread_pixtend.start()
Wie kann ich es programmieren, dass wenn ich diese Zeile auskommentiere, alles weg ist?
Hab schon einiges probiert und ich möchte es nicht zu kompliziert machen, wenn es nicht nötig ist bzw. gibt es ja gar keine Lösung.
Danke
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 15:44
von __deets__
Importe kommen an den Anfang, nicht irgendwo in den Code. Ein Objekt zu schliessen, dessen oeffnen fehlgeschlagen ist, ist sinnlos. Variablen, Funktionen und Methoden schreibt_man_klein_mit_unterstrich. KlassenWerdenCamelCaseGeschriebenOhneUnterstriche.
Die Instanz pixboard wird einfach beim erzeugen der KLASSE(!!) mit erzeugt. Das muss im Initialisator __init__ geschehen. Dann passiert das auch nicht immer, sondern nur, wenn ein Thread-Objekt erzeugt wird.
Das run in deinen Thread sieht falsch aus. Wenn die Bedingung startup_done nicht erfuellt ist, ist der Thread auch einfach vorbei. Womit die also entweder ueberfluessig ist, oder du sowas wie ein threading.Event willst, statt das selbst zu frickeln.
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 16:05
von Proxy87
Hallo __deets___,
ja ich weiß ich hab einige unschöne "ersten Schritte" gemacht. Muss hier mich ein wenig mehr an den Riemen reisen und korrekte Schreibweisen verwerden.
Der import in der Klasse ist nötig bei
Code: Alles auswählen
from pixtendlib import Pixtend
self.p = Pixtend() # Create instance
sonst wird die ohne RaspberryPi der Code nicht ausgführt, deshalb hab ich den DebugModus, um es am PC zu programmieren und am Raspberry zum laufen lassen, wenn es geht.
Der Schritt mit der Bedingung "if start.startup_done == True:" ist nötig, da ich 5 Threads parallel laufen lasse und erst nach einem Sonderthread, der nur einmalig läuft, die anderen in den Modus "run" erlaube, da hier dann aus einer XML File Daten gezogen worden sind.
Funktioniert auch super und die Bedingung ermöglicht das verzögerte und sichere Starten, falls eine Erstellung der Datenbank zu lange benötigt wird bei erstmaligen erstellen, falls sie nicht existiert als Backup, bevor sie in den RAM kopiert wird.
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 16:18
von __deets__
Es kann ja sein, dass das noetig ist. Es ist nur falsch programmiert. Denn danach ist der Thread beendet. Es reicht also voellig, den erst zu starten, wenn dieser andere "Sonderthread" gelaufen ist. Oder eben vernuenftig zu warten. Aber so wie's da steht ist es halt quatsch.
Und bezueglich der importe: so eine Weiche ist ja sinnvoll, aber eben nicht so. ZB kannst du schon auf Modulebene pruefen, ob das noetig ist, und dann ein gemocktes Interface anbieten:
Code: Alles auswählen
# meinmodul.py
import sys
if sys.platform == 'linux': # kann auch noch komplizierter werden
from das_plc_modul import MeinPLCDing
else:
from das_mock_plc_modul import MockPLCDing as MeinPLCDing
Generell hoert sich das alles viel zu kompliziert an. 5 Threads, die loslaufen, auch wenn's nicht sein muss, XML auf dessen Werte mit rohen Strings zugegriffen wird - das werden alles Fehlerquellen. Wuerde ich eindampfen, und vor allem auch deine XML-Datenstruktur einmal *sauber* in Python ueberfuehren.
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 16:25
von Sirius3
@Proxy87: eingerückt wird immer mit 4 Leerzeichen pro Ebene, keine Tabs.
Wenn Du pro Klasse eine py-Datei hast, dann ist das falsch. Module fassen mehrere Klassen/Funktionen zu einem logisch zusammengehörigen Block zusammen.
Woher kommt denn `start`? Wenn das wiederum ein Modul ist, das nur Konstanten enthält, wäre das auch eher falsch. In Python gibt es Wahrheitswerte, also sollte DEBUG_XML kein String sein.
Attributnamen müssen aussagekräftig sein, self.p ist das garantiert nicht.
Aus den Code-Fragmenten werde ich nicht wirklich schlau. Was soll das ganze eigentlich machen?
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 16:53
von Proxy87
Hallo,
nein ich verwende .py's nur zum strikteren des Programmcodes.
Die 5 Threads sind, meiner Meinung nach nötig, da sie unabhängig voneinander sein sollten. Und die Threads bleiben aktiv, auch mit der Bedingung, vielleicht Zufall. Ich behalte es mal im Hinterkopf für eine Fehleranalyse.
Code: Alles auswählen
Threading_PRG.thread_startup.start()
Threading_PRG.thread_temp.start()
Threading_PRG.thread_backup.start()
Threading_PRG.thread_flask.start()
Threading_PRG.thread_pixtend.start()
Threading_PRG.thread_Heiz.start()
Z. b. ist Start.py für die Auslesung der XML File gedacht, da ich hier einige Konfigurationen für das Programm durchführe. "Debug_XML" ist hier eine boolsche Variable, nur der VarName ist halt so um zu wissen woher es kommt. Meine Logik für Var Namen.
Code: Alles auswählen
path = os.getcwd() # der aktuelle Benutzerordner
daten = dom.parse("config.xml")
def findChildNodeByName(parent, name):
"""
Elementnamen aus der XML Datei lesen
:param parent:
:param name:
:return: Name der Funktion bzw. Variable in der XML File
"""
for node in parent.childNodes:
if node.nodeType == node.ELEMENT_NODE and node.localName == name:
return node
return None
def getText(nodelist):
rc = []
for node in nodelist:
if node.nodeType == node.TEXT_NODE:
rc.append(node.data)
return ''.join(rc)
# Daten aus der XML File der Gruppen laden
server = daten.getElementsByTagName('Server')[0]
typedata = daten.getElementsByTagName('Type')[0]
sql = daten.getElementsByTagName('SQL')[0]
wire = daten.getElementsByTagName('OneWire')[0]
pixend = daten.getElementsByTagName('Pixend')[0]
config = daten.getElementsByTagName('Config')[0]
# Unterpunkte auf dem <Server> auslesen, welche ausgegeben werden sollen
name = findChildNodeByName(server, 'Port')
if name is not None:
serverport = int(getText(name.childNodes))
name = findChildNodeByName(server, 'IPAdresse')
if name is not None:
if getText(name.childNodes) != '-':
serverip = getText(name.childNodes)
else:
serverip = socket.gethostbyname(socket.gethostname())
name = findChildNodeByName(server, 'Slots')
if name is not None:
serverslots = int(getText(name.childNodes))
# serverslots = getAttribute(name.childNodes)
# Unterpunkte auf dem <Type> auslesen, welche ausgegeben werden sollen
name = findChildNodeByName(typedata, 'Databasetype')
if name is not None:
databasetype = int(getText(name.childNodes))
# Unterpunkte auf dem <SQL> auslesen, welche ausgegeben werden sollen
name = findChildNodeByName(sql, 'User')
if name is not None:
sqluser = getText(name.childNodes)
name = findChildNodeByName(sql, 'Password')
if name is not None:
sqlpass = getText(name.childNodes)
name = findChildNodeByName(sql, 'Host')
if name is not None:
sqlhost = getText(name.childNodes)
name = findChildNodeByName(sql, 'Database')
if name is not None:
sqldatabase = getText(name.childNodes)
name = findChildNodeByName(sql, 'Time')
if name is not None:
sqltime = getText(name.childNodes)
name = findChildNodeByName(sql, 'Type')
if name is not None:
sqltype = getText(name.childNodes)
# Unterpunkte auf dem <SQL> auslesen, welche ausgegeben werden sollen
name = findChildNodeByName(wire, 'masterFolder')
if name is not None:
masterfolder = getText(name.childNodes)
name = findChildNodeByName(wire, 'masterfile')
if name is not None:
masterfile = getText(name.childNodes)
name = findChildNodeByName(wire, 'slaveFolder')
if name is not None:
slavefolder = getText(name.childNodes)
# Benutzerdefinierte Einträge in der XML Datei
name = findChildNodeByName(pixend, 'Relays')
if name is not None:
PixRelays = getText(name.childNodes)
else:
PixRelays = 4
name = findChildNodeByName(config, 'DelayBackup')
if name is not None:
DelayBackup = getText(name.childNodes)
else:
DelayBackup = 15
name = findChildNodeByName(config, 'DelaySensor')
if name is not None:
DelaySensor = getText(name.childNodes)
else:
DelaySensor = 5
name = findChildNodeByName(config, 'DelayPixend')
if name is not None:
DelayPixend = getText(name.childNodes)
else:
DelayPixend = 5
name = findChildNodeByName(config, 'DelayHeizen')
if name is not None:
DelayHeiz = getText(name.childNodes)
else:
DelayHeiz = 25
name = findChildNodeByName(config, 'Debug')
if name is not None:
DEBUG_XML = getText(name.childNodes)
else:
DEBUG_XML = False
name = findChildNodeByName(config, 'ThreadPixend')
if name is not None:
bThreadPixend = getText(name.childNodes)
else:
bThreadPixend = True
name = findChildNodeByName(config, 'ThreadFlask')
if name is not None:
bThreadFlask = getText(name.childNodes)
else:
bThreadFlask = True
name = findChildNodeByName(config, 'ThreadBackup')
if name is not None:
bThreadBackup = getText(name.childNodes)
else:
bThreadBackup = True
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 17:17
von Sirius3
Es darf keinen Ausführbaren Code auf oberster Ebene geben, weil man sich damit globale Variablen schafft, die ein Programm undurchsichtig machen.
Das dom-Interface ist sehr umständlich, praktisch jeder mit Python benutzt ElementTree.
`path` wird definiert, aber gar nicht benutzt, findChildNodeByName gibt es schon fertig, das muß man nicht selbst programmieren.
`daten` ist ein sehr schlechter Name für einen XML-Baum, der offensichtlich eine Konfiguration enthält.
Alles mögliche `name` zu nennen, ist auch nicht sehr übersichtlich.
Für Konfigurationen kann man z.B. eine Klasse definieren, die man dann überall dort übergeben kann, wo es gebraucht wird:
Code: Alles auswählen
import xml.etree.ElementTree as et
class Configuration:
def __init__():
config = et.parse("config.xml")
# Daten aus der XML File der Gruppen laden
server = config.find("Server")
self.serverport = int(server.findtext("Port", -1))
self.serverip = server.findtext("IPAdresse")
self.serverslots = int(server.findtext("Slots", -1))
# Unterpunkte auf dem <Type> auslesen, welche ausgegeben werden sollen
typedata = config.find("Type")
self.databasetype = int(typedata.findtext('Databasetype'))
# Unterpunkte auf dem <SQL> auslesen, welche ausgegeben werden sollen
sql = config.find('SQL')
self.sqluser = sql.findtext("User")
self.sqlpass = sql.findtext("Password")
self.sqlhost = sql.findtext("Host")
self.sqldatabase = sql.findtext("Database")
self.sqltime = sql.findtext("Time")
self.sqltype = sql.findtext("Type")
# Unterpunkte auf dem <OneWire> auslesen, welche ausgegeben werden sollen
wire = config.find('OneWire')
self.masterfolder = wire.findtext("masterFolder")
self.masterfile = wire.findtext("masterfile")
self.slavefolder = wire.findtext("slaveFolder")
# Benutzerdefinierte Einträge in der XML Datei
pixend = config.find('Pixend')
self.pixrelays = int(pixend.findtext("Relays", 4))
config_element = config.find('Config')
self.delay_backup = int(config_element.findtext('DelayBackup', 15))
self.delay_sensor = int(config_element.findtext('DelaySensor', 5))
self.delay_pixend = int(config_element.findtext('DelayPixend', 5))
self.delay_heizen = int(config_element.findtext('DelayHeizen', 25))
self.delay_debug = config_element.findtext('DelayDebug') == "True"
self.thread_pixend = config_element.findtext('ThreadPixend', "True") == "True"
self.thread_flask = config_element.findtext('ThreadFlask', "True") == "True"
self.thread_backup = config_element.findtext('ThreadBackup', "True") == "True"
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 17:43
von Proxy87
Sirius3 hat geschrieben: Samstag 2. Januar 2021, 17:17
Code: Alles auswählen
import xml.etree.ElementTree as et
class Configuration:
def __init__():
config = et.parse("config.xml")
# Daten aus der XML File der Gruppen laden
server = config.find("Server")
self.serverport = int(server.findtext("Port", -1))
self.serverip = server.findtext("IPAdresse")
self.serverslots = int(server.findtext("Slots", -1))
# Unterpunkte auf dem <Type> auslesen, welche ausgegeben werden sollen
typedata = config.find("Type")
self.databasetype = int(typedata.findtext('Databasetype'))
# Unterpunkte auf dem <SQL> auslesen, welche ausgegeben werden sollen
sql = config.find('SQL')
self.sqluser = sql.findtext("User")
self.sqlpass = sql.findtext("Password")
self.sqlhost = sql.findtext("Host")
self.sqldatabase = sql.findtext("Database")
self.sqltime = sql.findtext("Time")
self.sqltype = sql.findtext("Type")
# Unterpunkte auf dem <OneWire> auslesen, welche ausgegeben werden sollen
wire = config.find('OneWire')
self.masterfolder = wire.findtext("masterFolder")
self.masterfile = wire.findtext("masterfile")
self.slavefolder = wire.findtext("slaveFolder")
# Benutzerdefinierte Einträge in der XML Datei
pixend = config.find('Pixend')
self.pixrelays = int(pixend.findtext("Relays", 4))
config_element = config.find('Config')
self.delay_backup = int(config_element.findtext('DelayBackup', 15))
self.delay_sensor = int(config_element.findtext('DelaySensor', 5))
self.delay_pixend = int(config_element.findtext('DelayPixend', 5))
self.delay_heizen = int(config_element.findtext('DelayHeizen', 25))
self.delay_debug = config_element.findtext('DelayDebug') == "True"
self.thread_pixend = config_element.findtext('ThreadPixend', "True") == "True"
self.thread_flask = config_element.findtext('ThreadFlask', "True") == "True"
self.thread_backup = config_element.findtext('ThreadBackup', "True") == "True"
Sieht wirklich deutlich strukturierter aus und ich muss nicht alles selber programmieren. Irgendwie befürchte ich, wenn ich meinen Code weiter offen legen würde, dass es zu 90% zu aufwendig ist.
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 18:27
von __deets__
Ich wuerde auch gleich auf das viel zu umstaendliche XML verzichten, und mit JSON arbeiten. Damit bekommst du gleich eine vernuenftige Typwandlung.
Re: [Klassen/Objekte] Initiieren erst nach dem Call
Verfasst: Samstag 2. Januar 2021, 22:16
von Sirius3
Wenn es um Konfigurationen geht, würde ich auf JSON verzichten und gleich YAML nehmen, dass ist viel einfacher zu schreiben und zu lesen.