[Klassen/Objekte] Initiieren erst nach dem Call

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
Proxy87
User
Beiträge: 4
Registriert: Samstag 2. Januar 2021, 14:31

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
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

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.
Proxy87
User
Beiträge: 4
Registriert: Samstag 2. Januar 2021, 14:31

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.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

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.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

@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?
Proxy87
User
Beiträge: 4
Registriert: Samstag 2. Januar 2021, 14:31

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
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

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"
Proxy87
User
Beiträge: 4
Registriert: Samstag 2. Januar 2021, 14:31

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.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich wuerde auch gleich auf das viel zu umstaendliche XML verzichten, und mit JSON arbeiten. Damit bekommst du gleich eine vernuenftige Typwandlung.
Sirius3
User
Beiträge: 18274
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn es um Konfigurationen geht, würde ich auf JSON verzichten und gleich YAML nehmen, dass ist viel einfacher zu schreiben und zu lesen.
Antworten