AttributeError: 'NoneType' object has no attribute 'set'

Fragen zu Tkinter.
Antworten
schlake042
User
Beiträge: 7
Registriert: Dienstag 19. Februar 2019, 18:30

Mittwoch 6. November 2019, 19:15

Hey Freunde,

mein Vorhaben ist es, 4 Sensorwerte zu simulieren und diese nacheinander "aktivieren" zu können und in 4 Labels die Werte darzustellen und jede Sekunde zu aktualisieren.
Mein Code orientiert sich an diesem Beispiel viewtopic.php?t=3869 . Beim ersten Label und beim ersten Sensor funktioniert das auch. Beim zweiten-dritten-vierten kriege ich immer folgende Fehlermeldung:

Code: Alles auswählen

Exception in thread Thread-50:
Traceback (most recent call last):
  File "D:\Anaconda\lib\threading.py", line 917, in _bootstrap_inner
    self.run()
  File "C:/Users/xx/Desktop/Tkinter/xx.py", line 70, in run
    self.motormeldungen1.set(value)
AttributeError: 'NoneType' object has no attribute 'set'

An exception has occurred, use %tb to see the full traceback.
Ich habe natürlich schon sehr lange gegoogled. Häufig wird gesagt, dass die Grid-Methode den Rückgabewert None hat. Meiner Meinung nach definiere ich die Variabel aber korrekt.
Hier der Teil meines Codes.

Code: Alles auswählen


Threading mit Event - Beispiel
"""

import threading
import time
import tkinter
import sys
import random



class Motorsteuerung(threading.Thread):
    """
    Simulierte Schrittmotorsteuerung
    """
   
    def __init__(self, motormeldungen1 = None, motormeldungen2 = None, motormeldungen3 = None, motormeldungen4 = None):
        """
        schalter = threading.Event
        motormeldungen = Rückgabe als tkinter.StringVar, damit eine
                         Meldung in einem Label angezeigt werden kann.
        """
       
        threading.Thread.__init__(self)
       
        self.schalter1 = threading.Event()
        
        
        
        self.motormeldungen1 = motormeldungen1
        self.motormeldungen2 = motormeldungen2
        self.motormeldungen3 = motormeldungen3
        self.motormeldungen4 = motormeldungen4
        
       
        
        self.canceled1 = False
        


    def run(self):
        """
        Der Motor wird initialisiert.
        Enthaelt die Steuerschleife.
        """
      
        i = 0
       
        while True:
            # Hier wird darauf gewartet, dass der Schalter
            # eingeschaltet wird.
            
            self.schalter1.wait()
            if self.canceled1:
                break
           
            # Statt einer Schrittmotor-Aansteuerung wird hier einfach
            # ein Text an die Variable "motormeldungen" uebergeben.
            # Statt mit einer tkinter.StringVar könnte man natürlich
            # auch mit print etwas anzeigen lassen.
            i += 1
            if sensor1 == True:
                value = random.random() * (35-10) + 10 
                self.motormeldungen1.set(value)
                print("S1")
                print (value)

            if sensor2 == True:
                value1 = random.random() * (35-10) + 10 
                 
                self.motormeldungen2.set(value1)
                print("S2")
                print (value1)  
                
            if sensor3 == True:
                value2 = random.random() * (35-10) + 10 
                self.motormeldungen3.set(value2)
                print("S3")
                print (value2)
                
            if sensor4 == True:
                value3 = random.random() * (35-10) + 10 
                self.motormeldungen4.set(value3)
                print("S4")
                print (value3)
           
           # else:
            #    print(random.random() * (35-10) + 10 )
            time.sleep(1)
            
            
       
           
    def s1_start(self):
        """
        Schaltet den Motor ein
        """
        self.schalter1.set()
        global sensor1
        sensor1 = True
        

    def s2_start(self):
        """
        Schaltet den Motor ein
        """
        self.schalter1.set()
        global sensor2
        sensor2 = True
        
    def s3_start(self):
        """
        Schaltet den Motor ein
        """
        self.schalter1.set()
        global sensor3
        sensor3 = True
        
    def s4_start(self):
        """
        Schaltet den Motor ein
        """
        self.schalter1.set()
        global sensor4
        sensor4 = True

    def motor_stopp(self):
        """
        Stoppt den Motor aus
        """
        global sensor1
        global sensor2
        global sensor3
        global sensor4
        sensor1 = False
        sensor2 = False
        sensor3 = False
        sensor4 = False
        self.schalter1.clear()
   

    def motor_aus(self):
        """
        Schaltet den Motor aus
        """
        
        
        self.canceled1 = True
        self.schalter1.set()
   

class MeinFenster(tkinter.Tk):
    """
    GUI
    """
   
    def _center(self, *args):
        """
        Zentriert das Fenster
        """
        xpos = (self.winfo_screenwidth() - self.winfo_width()) / 2
        ypos = ((self.winfo_screenheight() - self.winfo_height()) / 2) / 100 * 90
        self.wm_geometry("+%d+%d" % (xpos,ypos))

   
    def motor_aus(self):
        """
        Schaltet die Schleife aus und schliesst das Fenster
        """
        self.motor.motor_aus()
        self.destroy()
       

    def __init__(self):
        """
        Anzeigen und initialisieren
        """
       
        # Init
        tkinter.Tk.__init__(self)
       
        self.motormeldungen1 = tkinter.StringVar()
        self.motormeldungen2 = tkinter.StringVar()
        self.motormeldungen3 = tkinter.StringVar()
        self.motormeldungen4 = tkinter.StringVar()
        

        # Motor initialisieren
        self.motor = Motorsteuerung(self.motormeldungen1)
        self.motor1 = Motorsteuerung(self.motormeldungen2)
        self.motor2 = Motorsteuerung(self.motormeldungen3)
        self.motor3 = Motorsteuerung(self.motormeldungen4)
        
        
        self.motor.start()
        self.motor1.start()
        self.motor2.start()
        self.motor3.start()

        # Fenster und Buttons
        self.config(bd = 10)
        frame = tkinter.Frame(self)
        frame.pack()
        tkinter.Button(
            frame,
            text = "Start Sensor 1",
            command = self.motor.s1_start,
            bd = 10, padx = 10, pady = 10
        ).grid(row = 0, column = 0)
        tkinter.Button(
            frame,
            text = "Stopp",
            command = self.motor.motor_stopp,
            bd = 10, padx = 10, pady = 10
        ).grid(row = 4, column = 0)
        tkinter.Button(
            frame,
            text = "Start Sensor 2",
            command = self.motor.s2_start,
            bd = 10, padx = 10, pady = 10
        ).grid(row = 1, column = 0)
        tkinter.Button(
            frame,
            text = "Start Sensor 3",
            command = self.motor.s3_start,
            bd = 10, padx = 10, pady = 10
        ).grid(row = 2, column = 0)
        tkinter.Button(
            frame,
            text = "Start Sensor 4",
            command = self.motor.s4_start,
            bd = 10, padx = 10, pady = 10
        ).grid(row = 3, column = 0)
       
        # Label
        lab = tkinter.Label(
            self,
            text = "Fill1",
            textvariable = self.motormeldungen1,
            bd = 10, padx = 10, pady = 10
        ).pack()
       
        lab2 = tkinter.Label(
            self,
            text = "Fill2",
            textvariable = self.motormeldungen2,
            bd = 10, padx = 10, pady = 10
        ).pack()
        lab3 = tkinter.Label(
            self,
            text = "Fill3",
            textvariable = self.motormeldungen3,
            bd = 10, padx = 10, pady = 10
        ).pack()
        # Aus
        lab4 = tkinter.Label(
            self,
            text = "Fill4",
            textvariable = self.motormeldungen4,
            bd = 10, padx = 10, pady = 10
        ).pack()
        tkinter.Button(
            self,
            text = "Motor AUS",
            command = self.motor_aus,
            bd = 4
        ).pack()
        
       
        # Warten bis das Fenster angezeigt wird und dann zentrieren
        self.wait_visibility()
        self._center()
       

if __name__ == "__main__":
    fenster = MeinFenster()
    tkinter.mainloop()
    sys.exit(0)
                
                
                
Habt ihr eine Idee? Und sorry für die "Namenswahl". Ich habe die Variabeln noch nicht vernünftig geändert.
Vielen Dank!

Mfg
Nick
Sirius3
User
Beiträge: 10900
Registriert: Sonntag 21. Oktober 2012, 17:20

Mittwoch 6. November 2019, 19:39

Vergiss gleich wieder, dass es soetwas wie `global` gibt, vor allem in Zusammenhang mit Threads. GUIs dürfen nur aus dem Hauptthread heraus verändert werden, `self.motormeldungen1.set` in `run` ist verboten; das muß man über Queues und regelmäßiger Abfrage im Hauptthread per `after` lösen. Du machst für alle vier Sensoren quasi das selbe, das solltest Du im Code nur einmal haben und mit z.B. Listen arbeiten. Du startest 4 Threads, im Hauptthread nennst Du die zwar motormeldungen1 bis 4, in Motorsteuerung ist das aber immer motormeldungen1. Du benutzt trotzdem alle 4. Das ist auch ein Grund, warum man nicht Defaultwerte in Methoden haben sollte, die keinen Default (hier None) haben. Dann wäre der Fehler gleich aufgefallen. `lab` bis `lab4` werden umständlich mit None belegt, aber nie gebraucht. Das `sys.exit(0)` ist überflüssig und kann weg.
Benutzeravatar
__blackjack__
User
Beiträge: 4681
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mittwoch 6. November 2019, 21:04

Ich sehe auch nicht so ganz wofür man hier den Thread braucht — das sollte auch mit `after()` lösbar sein.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
schlake042
User
Beiträge: 7
Registriert: Dienstag 19. Februar 2019, 18:30

Mittwoch 6. November 2019, 22:06

Hey,

Danke für eure Antworten.

Ja ich wollte das Programm so gestalten, dass wenn ich den ersten Button (Sensor 1) drücke, nur Werte für den ersten Sensor kommen. Wenn ich dann beispielsweise auf den Button für Sensor 3 drücke.. Dann kommen Werte für 1 und 3. Also sozusagen, dass sich das Programm immer automatisch erweitert. Deswegen dachte ich, dass ich Threads benutzen muss/soll/kann.
Benutzeravatar
__blackjack__
User
Beiträge: 4681
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 30. November 2019, 12:52

@schlake042: Noch mal ein paar Anmerkungen:

Auch `fenster` sollte nicht auf Modulebene definiert sein.

`Mein` ist ein überflüssiger Präfix wenn es nicht auch `Unser` oder `Ihr` oder `Sein` gibt wovon man das `Mein` unterscheiden können muss.

Die `__init__()` ist die erste Methode die definiert wird. Wenn die nicht an erster Stelle steht, kann es leicht passieren das ein Leser sie übersieht und denkt es gibt für die Klasse keine eigene Initialisierung.

Docstrings und Kommentare die nur offensichtliches enthalten braucht man nicht. Faustregel zu Kommentaren: Nicht kommentieren *was* gemacht wird, denn das steht da bereits als Code, sondern *warum* der Code das so macht. Sofern das nicht offensichtlich ist. Der Kommentar ``# Aus`` steht nicht an der Stelle wo er sollte, wenn man ihn denn überhaupt schreiben sollte.

Durchnummerierte Namen sind in der Regel ein Zeichen das man etwas falsch macht. Entweder möchte man sich bessere Namen ausdenken, oder gar keine einzelnen Namen verwenden, sondern eine Datenstruktur. Oft eine Liste. Wenn man nur eine 1 angehängt hat ist das in aller Regel komplett sinnlos.

`motormeldungen1` bis `motormeldungen4` in `MeinFenster` braucht man nicht als Attribute. Und die ganzen Sachen die dort 4× stehen, sollten in einer Schleife abgearbeitet werden.

Es macht keinen Sinn die `text`-Option an `Label` zu übergeben wenn man gleichzeitig `textvariable` übergibt, weil der `text` dann nie angezeigt wird.

Warum nimmt die `_center()`-Methode eine beliebige Anzahl von Argumenten entgegen die ignoriert werden und bein Aufruf gar nicht angegeben werden‽

Warum gibt es die globalen `sensor*`-Variablen? Ob ein Sensor Daten liefern soll oder nicht wird doch bereits durch `self.schalter` festgelegt.

`i` in `Motorsteuerung.run()` wird nicht verwendet.

Zwischenstand immer noch mit Threads und einer dazwischengeschalteten Queue, damit das thread-sicher wird:

Code: Alles auswählen

#!/usr/bin/env python3
import queue
import random
import threading
import time
import tkinter

VALUE_INTERVAL = 1  # in seconds.


class Motorsteuerung(threading.Thread):
    def __init__(self, meldungs_queue, motormeldungen):
        threading.Thread.__init__(self, daemon=True)
        self.meldungs_queue = meldungs_queue
        self.motormeldungen = motormeldungen
        self.schalter = threading.Event()
        self.canceled = False

    def run(self):
        while True:
            self.schalter.wait()
            if self.canceled:
                break

            value = random.random() * (35 - 10) + 10
            self.meldungs_queue.put((self.motormeldungen, value))
            print(value)
            time.sleep(VALUE_INTERVAL)

    def motor_start(self):
        """
        Schalte den Motor ein.
        """
        self.schalter.set()

    def motor_stopp(self):
        """
        Stoppe den Motor.
        """
        self.schalter.clear()

    def motor_aus(self):
        """
        Schalte den Motor aus.
        """
        self.canceled = True
        self.schalter.set()


class Fenster(tkinter.Tk):
    def __init__(self):
        tkinter.Tk.__init__(self)
        self.config(bd=10)

        frame = tkinter.Frame(self)
        frame.pack()

        self.motormeldung_queue = queue.Queue()
        self.motorsteuerungen = list()
        for i in range(4):
            motormeldung = tkinter.StringVar()
            motorsteuerung = Motorsteuerung(
                self.motormeldung_queue, motormeldung
            )
            motorsteuerung.start()
            self.motorsteuerungen.append(motorsteuerung)

            tkinter.Button(
                frame,
                text=f"Start Sensor {i + 1}",
                command=motorsteuerung.motor_start,
                bd=10,
                padx=10,
                pady=10,
            ).grid(row=i, column=0)
            tkinter.Label(
                self, textvariable=motormeldung, bd=10, padx=10, pady=10
            ).pack()

        tkinter.Button(
            frame,
            text="Stopp",
            command=self.stoppe_motoren,
            bd=10,
            padx=10,
            pady=10,
        ).grid(row=i + 1, column=0)

        self._center()
        self.verarbeite_meldungs_queue()

    def _center(self):
        """
        Zentriere das Fenster.
        """
        self.wait_visibility()
        xpos = int((self.winfo_screenwidth() - self.winfo_width()) / 2)
        ypos = int(
            ((self.winfo_screenheight() - self.winfo_height()) / 2) * 0.9
        )
        self.wm_geometry(f"+{xpos}+{ypos}")

    def stoppe_motoren(self):
        for motorsteuerung in self.motorsteuerungen:
            motorsteuerung.motor_stopp()

    def verarbeite_meldungs_queue(self):
        try:
            while True:
                variable, value = self.motormeldung_queue.get_nowait()
                variable.set(f"{value:.5f}")
        except queue.Empty:
            pass  # Intentionally ignored.

        self.after(VALUE_INTERVAL * 1000 // 2, self.verarbeite_meldungs_queue)


def main():
    fenster = Fenster()
    fenster.mainloop()


if __name__ == "__main__":
    main()
Aber wie schon gesagt braucht man da eigentlich keine Threads, denn das lässt sich auch problemlos mit `after()` lösen, was man ja so oder so benötigt.
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
Antworten