Flask inherente Klassen

Django, Flask, Bottle, WSGI, CGI…
Antworten
tschaka81
User
Beiträge: 31
Registriert: Donnerstag 24. Januar 2019, 08:15

Hallo,

ich hätte da mal eine Frage bezüglich von Flask und Klassen.

Ich habe mir derzeit eine Klasse gebaut

Code: Alles auswählen

class sqlinit():
     def openconnection(self, config):
        pass

class Apex(FlaskView, sqlinit):
    def __init__(self):
        #super().__init__()
        pass
        
    def run(self, config):
        self.config = config
        super().openconnection(self.config)
        self.data = []
        from threading import Thread
        x = Thread(target=self.getstatus) 
        x.start()
        return self

    def getstatus(self):
        k=0
        while True:
            time.sleep(2)
            k+=1
            print(k)
            self.data = {"data": k}

    @route('/')
    def index(self):
        return "<h1>This is my Apex class</h1>"


    @route('/apexstatus')
    def apexstatus(self):
        return self.data
        

if __name__ == '__main__':
    config = {}
    app = Flask(__name__)
    apex = Apex().run(config).register(app,route_base = '/')
    app.run(debug=True) 
(mein Code ist noch umfangreicher, tut aber wahrscheinlich hier nichts zur Sache, ich hab hier mal ein Minimalbeispiel draus gemacht.)

Mein Problem ist jetzt folgendes:
Wie man im Code erkennen kann wird die function getstatus als Thread aufgerufen und es werden Daten abgefragt. Die Daten werden dann in die Variable self.data geschrieben. Wenn ich mir das per Print anschaue funktioniert das auch so wie es sein soll.
Wenn ich jetzt aber über Flask die Webseite öffne und den Link /apexstatus ausprobiere, so bekomme ich keine aktuellen Daten, sondern nur die, mit welchem ich mein Objekt initialisiert habe. (Also nur die aus der __init__(self), nicht aber die aus der run(self, config)


Es scheint mir so, dass bei der Initialisierung des Objektes die Werte gesetzt werden und dann zwei "verschiedene" self variablen durch mein Objekt geistern. Kann es sein, dass der Thread hier irgendetwas komisches macht?

Ich bekomme zumindest als Fehler beim Aufrufen, dass self.data nicht in Apex vorhanden ist. Was mache ich falsch???
Benutzeravatar
__blackjack__
User
Beiträge: 13064
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@tschaka81: Da `FlaskView` unbekannt ist kann man nicht sagen ob es einen Unterschied macht, dass Du aktiv verhinderst, das dessen `__init__()` ausgeführt wird. Warum machst Du das?

Klassennamen werden in Python in PascalCase benannt. Und ein Paar von den Namen könnten Unterstriche zwischen den Worten vertragen.

``super().open_connection(self.config)`` sollte ``self.open_connection(self.config)`` sein. Und warum ist `config` ein Argument? Das ist doch auch über `self` erreichbar. Darum ist das doch eine Methode. Wobei ich mich auch Frage ob das zwingend ein Mixin sein muss? Hätte man das nicht über Vererbung lösen können, oder gar als Komposition?

Importe gehören an den Anfang vom Modul, nicht in irgendwelche Funkionen oder Methoden versteckt.

`x` ist kein guter Name für einen `Thread`, und letztlich wird der Name auch überhaupt gar nicht gebraucht.

In den meisten Fällen will man Threads als `daemon`-Threads starten, oder es wird manchmal problematisch ein Programm zu beenden.

Attribute sollten nach Ablauf der `__init__()` existieren und nicht in Methoden später noch hinzugefügt werden. Damit würdest Du zumindest den `AttributeError` schon mal loswerden.

Und falls `openconnection()` neue Attribute einführt die von anderen Methoden benutzt werden die nicht zur Mixin-Klasse gehören, dann sollte das definitiv *kein* Mixin sein.

Warum ist `self.data` mal eine Liste und mal ein Wörterbuch?

`run()` hat einen irreführenden Namen weil der Leser da erwartet das etwas blockierten läuft und die Methode erst zurückkehrt wenn alles gelaufen und fertig/am Ende ist. Siehe `Flask.run()`.

`getstatus()` hat einen irreführenden Namen weil der Leser da eine Getter-Methode erwartet die man aufrufen kann und einen Status als Rückgabewert bekommt.

In Python-Kreisen wird dieser „fluid API“-Stil oft als „train wreck calls“ bezeichnet: das wird nicht wirklich gerne gesehen. Das ist okay wenn Aufrufe ein neues Ergebnis liefern, aber nicht wenn alle Methoden die kein sinnvolles Ergebnis haben einfach `self` zurück geben.

Zwischenstand:

Code: Alles auswählen

class SqlInitMixin:
    def open_connection(self):
        pass


class Apex(FlaskView, SqlInitMixin):
    def __init__(self, config):
        FlaskView.__init__(self)
        self.config = config
        self.data = {}

    def start_status_updates(self):
        self.open_connection()
        Thread(target=self.update_status, daemon=True).start()

    def update_status(self):
        k = 0
        while True:
            time.sleep(2)
            k += 1
            print(k)
            self.data = {"data": k}

    @route("/")
    def index(self):
        return "<h1>This is my Apex class</h1>"

    @route("/apexstatus")
    def apex_status(self):
        return self.data


def main():
    config = {}
    app = Flask(__name__)
    
    apex = Apex(config)
    apex.start_status_updates()
    apex.register(app, route_base="/")
    
    app.run(debug=True)


if __name__ == "__main__":
    main()
Jetzt hat das aber das Problem, dass Du Threads startest. Also potentiell Mehrzahl, denn es ist grundsätzlich erst einmal nicht garantiert, dass es nur ein `Apex`-Objekt gibt. Der Server kann am Ende mehrere Threads mit deiner App starten oder mehrere Prozesse, oder gar mehrere Prozesse in dem meherere Threads laufen. Also ja, es kann mehrere `self` geben.

Allgemeinen, gemeinsamen Zustand in Webanwendungen speichert man deshalb üblicherweise in einem extre Server/Dienst. In der Regel eine Datenbank.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
tschaka81
User
Beiträge: 31
Registriert: Donnerstag 24. Januar 2019, 08:15

Hallo Blackjack,

danke erstmal für deine Antwort.

Ich bin leider kein Phythonc crack und werde mir deine Kommentare nochmal ansehen. Ich habe zwischenzeitlich das Problem gelöst, auch wenn ich noch nicht verstehe warum es so geht:

Ich musste das

Code: Alles auswählen

data = {"data": 0}  
aus der __init__ herausnehmen und direkt unter die Klasse packen. Zusätzlich musste ich die Variable dann von self auf Apex stellen. Dann hat auch der Thread erkannt, worum es geht.

Code: Alles auswählen

class sqlinit():
     def openconnection(self, config):
        pass

class Apex(FlaskView, sqlinit):

    data = {"data": 0}    
    
    def __init__(self):
        pass
        
    def runapex(self, config):
        self.config = config
        self.openconnection(self.config)
        Thread(target=self.getstatus).start() 
        return self
    
    def getstatus(self):
        k=0
        while True:
            time.sleep(2)
            k+=1
            Apex.data = {"data": k}


    @route('/')
    def index(self):
        return "<h1>This is my Apex class</h1>"


    @route('/apexstatus')
    def apexstatus(self):
        return {"return": Apex.data}
     


if __name__ == '__main__':
    config = {}
    app = Flask(__name__)
    apex = Apex().runapex(config).register(app,route_base = '/')
    app.run(debug=True) 
Benutzeravatar
__blackjack__
User
Beiträge: 13064
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@tschaka81: Das ist keine Lösung. Das mag funktionieren, ist aber nicht garantiert. Nimm eine Datenbank, das löst das Problem, egal wie und wieviele Threads/Prozesse der Server am Ende verwendet.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
tschaka81
User
Beiträge: 31
Registriert: Donnerstag 24. Januar 2019, 08:15

Hallo blackjack,

ich mag dir hier nicht widersprechen, da du für den allgemeinen Fall sicherlich recht hast. In meinem dedizierten Fall ist es aber so, dass die entsprechende Klasse immer genau einmal ausgeführt wird. Eigentlich ist eine Klasse für meinen Anwendungsbereich total falsch. Allerdings hat das wieder Gründe meiner eigenen verquerten Ordnung und der späteren Verfügbarkeit von den Modulen. Es ist sogar in meinem Fall so, dass die Daten aus einer Datenbank kommen und weitergegeben werden, es also in meinem Fall keinen Sinn macht sie wieder zwischenzuspeichern. Davon habe ich allerdings nichts erwähnt und ohne Kugel kann man das ja auch nicht wissen.

In jedem Fall hast du mir schon mal gut weitergeholfen:

Vielleicht kannst du mir aber noch den Unterschied zwischen:

Code: Alles auswählen

class Test1():
    a = "hallo"
    def __init__(self):
        pass
        
    def seta(self):
        Test1.a = "duda"
und

Code: Alles auswählen

class Test1():
    def __init__(self):
        self.a = "hallo"
    
    def seta(self):
        self.a = "duda"
erklären?

Nach meinem Verständnis hatte ich immer Fall 2 benutzt, da ich dachte, dass self sich auf das Object Test1 bezieht. Im Falle meines Threads aus dem vorherigen Beispiel war aber Test1 und self nicht gleich. Würde ich bei einem neuen Erzeugen von Fall1 jetzt die Daten zwischen Object1 und Object2 gegenseitig austauschen? Was ist der reale Unterschied, bzw. was kann denn hier schiefgehen, was ich nicht sehe?
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

tschaka81 hat geschrieben: Donnerstag 4. März 2021, 07:36In meinem dedizierten Fall ist es aber so, dass die entsprechende Klasse immer genau einmal ausgeführt wird.
Das stimmt eben nicht, wie Du mit Deinem Code ja selbst zeigst. Klassenvariablen, wie Du sie jetzt benutzt, sind eigentlich nichts anderes als globale Variablen
Antworten