Wiederholen einer API Abfrage und Darstellung der neuen Daten

Fragen zu Tkinter.
Antworten
blabla193
User
Beiträge: 7
Registriert: Donnerstag 2. Mai 2019, 10:34

Hallo zusammen,

Ich stehe jetzt schon eine Weile auf dem Schlauch. Und zwar baue ich mir ein Programm, mit dem unter anderem die aktuellen Wetterdaten über eine API angefragt und dann ausgegeben werden.
Problem dabei: das Programm läuft nur einmal durch, d.h. es werden stundenlang eben die selben Werte angezeigt.
Ich komme nicht darauf wie ich hier Schleifen einsetzen könnte, alles was ich versuche führt dazu, dass die GUI nicht mehr angezeigt wird oder ändert nichts am Problem.

Ich hoffe hier kann mir jemand weiterhelfen, vielen Dank im voraus.

https://github.com/Blabla19338/API/blob/master/Code
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dein Programm benutzt zwar after, um die Zeit weiter zu treiben, aber in deinen ticks fragst du doch deine Wetter-API nicht ab. Wie soll es also zu einem update kommen? Und die Vermischung von Abfrage und Erstellung von GUI ist an dieser Stelle natuerlich sehr kontraproduktiv, denn dadurch kannst du Abfrage (falsch benamt, Funktionen schreibt man in Python klein_und_mit_unterstrich) auch nicht einfach schedulen.

Trenn also die Abfrage in Erstellung der GUI und Ermittlung der fuer dich interessanten Werte auf. Und diese Werte musst du dann zB per StringVar oder element.config in den entsprechenden GUI-Elementen updaten.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@blabla193: Schleifen kannst Du da nicht für verwenden, und die `after()`-Methode kennst Du ja offensichtlich schon. Damit geht das.

Du solltest dann auch mal den Sternchen-Import und die ganzen ``global``-Anweisungen loswerden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
blabla193
User
Beiträge: 7
Registriert: Donnerstag 2. Mai 2019, 10:34

__deets__ hat geschrieben: Donnerstag 2. Mai 2019, 10:54 Dein Programm benutzt zwar after, um die Zeit weiter zu treiben, aber in deinen ticks fragst du doch deine Wetter-API nicht ab. Wie soll es also zu einem update kommen? Und die Vermischung von Abfrage und Erstellung von GUI ist an dieser Stelle natuerlich sehr kontraproduktiv, denn dadurch kannst du Abfrage (falsch benamt, Funktionen schreibt man in Python klein_und_mit_unterstrich) auch nicht einfach schedulen.

Trenn also die Abfrage in Erstellung der GUI und Ermittlung der fuer dich interessanten Werte auf. Und diese Werte musst du dann zB per StringVar oder element.config in den entsprechenden GUI-Elementen updaten.
Also würde es schon reichen wenn ich in ticks einen Abruf von Abfrage reinpacke?:D

Und das mit der Aufteilung verstehe ich nicht ganz, da ich ja die Variable data zB nur in der Funktion drin habe kann ich ja die GUI später nicht mit davon abhängigen Werten erstellen oder?
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein, das wuerde es nicht, aus dem Grund, den du noch nicht verstanden hast: weil du in Abfrage ZWEI Sachen machst: deine Darstellung aufzubauen, UND die Abfrage. Das ist so, als ob du erst den Kirchturm baust, und dann den Wetterhahn in die richtige Richtung drehst. Wenn du den dann immer wieder einstellen willst, baust du dann jedes mal wieder den gesamten Kirchturm? Und vor allem was passiert, wenn da schon einer steht? Das mindeste was du dann tun muesstest waere wohl, den vorher abzureissen. Kann man machen. Sieht halt scheisse aus, weil die GUI dann hin-und-her-flackert, so wie die Leute auch statt eines Kirchturms mit nett drehendem Wetterhahn eine permanente Baustelle sehen, die gelegentlich mal die Windrichtung anzeigt.

Und wie dir unklar ist, wie du neue Wetter-Werte eintraegst, aber gleichzeitig in der Lage bist, neue Zeiten einzutragen - das verstehe ich nicht. Was ist denn an einer Zahl Minuten anders als an einer Zahl Temperatur? Warum ist das eine eine Huerde, nicht aber das andere?

Das du das unter Verwendung von globalen Variablen machst, ist zwar boese(tm) aus verschiedenen Gruenden, aber das ist dann der naechste Schritt.
blabla193
User
Beiträge: 7
Registriert: Donnerstag 2. Mai 2019, 10:34

Ich verstehe nur nicht, wie ich die GUI Erstellung und die Abfrage der Werte trennen kann.
Und um ehrlich zu sein, die funktionen zur Uhrzeit habe ich nur kopiert, da ich die selbst vermustlich auch nicht hinbekommen hätte :'D..
__deets__
User
Beiträge: 14543
Registriert: Mittwoch 14. Oktober 2015, 14:29

Dann sieh dir die Uhrzeit-Funktionen doch mal genau an, und versuch zu verstehen, was die tun. Und wie sich das zu deiner alles-in-einem-ding-machen-Abfrage verhaelt. Denn die loesen das Problem, ein GUI-Element spaeter mit neuen Werten zu bestuecken.
blabla193
User
Beiträge: 7
Registriert: Donnerstag 2. Mai 2019, 10:34

Alles klar, ich werds versuchen.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

@blabla193: noch ein paar Anmerkungen zum Code.
Die Kommentarzeilen mit den vielen --- sehen für Dich vielleicht nett aus, für jeden, der es gewohnt ist Code zu lesen, sind sie aber nur störend. Code struktueriert man am besten durch Funktionen. Das gesagt, mischst Du den Aufbau der GUI mit Funktionsdefinitionen. Der Aufbau der GUI sollte auch in einer Funktion stehen, das verhindert auch, dass man ungewollt globale Variablen verwendet.
Variablen schreibt man wie Funktionen klein_mit_unterstrich. Abkürzungen vermeiden. Unter Sauf-Gang stelle ich mir Besoffene auf der Straße vor. API-Keys sollte man nicht in öffentlichen Repositories stehen lassen.
`place` sollte man nicht verwenden, da es je nach System, Bildschirmauflösung, etc. anders dargestellt wird und unter Umständen unlesbar wird. Benutze grid oder place oder beides in Kombination mit Frames.
Dass tag, tick und tick2 nicht mit der selben Uhrzeit neu gesetzt werden, führt zu fehlerhafter Anzeige immer dann, wenn eine Minute oder ein Tag umspringt. Besser alles mit einem time-Aufruf erledigen, noch besser, dafür datetime.now benutzen. Irgendwie wird wochentag gar nicht aktualisiert.

Um keine globalen Variablen zu nutzen, wirst Du zwangsläufig Klassen definieren müssen.
blabla193
User
Beiträge: 7
Registriert: Donnerstag 2. Mai 2019, 10:34

Das mit SAufgang hab ich mir auch schon gedacht, aber war mir dann egal:D
Das mit der Uhrzeit und dem Datum ist komisch, denn bei mir funktioniert das dass es normal umspringt, auch bei einem Tagwechsel.
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Dass etwas so aussieht, als ob es funktioniert, heißt noch nicht, dass es auch richtig funktioniert. Bei diesem konkreten Fall sind es zwar normalerweise maximal ein paar Bruchteile einer Sekunde, dass falsche Angaben zu sehen sind, falsch ist es trotzdem und vor allem so unnötig falsch, weil man es viel übersichtlicher in einer Funktion schreiben könnte.
blabla193
User
Beiträge: 7
Registriert: Donnerstag 2. Mai 2019, 10:34

Sirius3 hat geschrieben: Donnerstag 2. Mai 2019, 12:51 Dass etwas so aussieht, als ob es funktioniert, heißt noch nicht, dass es auch richtig funktioniert. Bei diesem konkreten Fall sind es zwar normalerweise maximal ein paar Bruchteile einer Sekunde, dass falsche Angaben zu sehen sind, falsch ist es trotzdem und vor allem so unnötig falsch, weil man es viel übersichtlicher in einer Funktion schreiben könnte.
Wie genau in eine Funktion? Habe jetzt gefühlt alles versucht und trotzdem hängt die Sekundenanzeige hin und wieder mal...
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@blabla193: Eine Funktion die das macht was vorher die drei Funktionen gemacht haben, in der eben nur *einmal* die aktuelle Zeit + Datum ermittelt wird, und nicht drei mal.

Ansonsten könnte natürlich noch die Abfrage der Wetter-API ein Problem sein, denn sowie die über eine Sekundengrenze hinweg lang die GUI blockiert, hängt natürlich auch die Uhr an der Stelle.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
blabla193
User
Beiträge: 7
Registriert: Donnerstag 2. Mai 2019, 10:34

Wie könnte ich verhindern dass die API mehr als eine Sekunde blockiert? Ist das die Zeit in .after()?
Sirius3
User
Beiträge: 17754
Registriert: Sonntag 21. Oktober 2012, 17:20

Nein, sondern indem Du die API-Abfrage in einen eigenen Thread auslagerst, mit all seinen Komplikationen.
Benutzeravatar
__blackjack__
User
Beiträge: 13116
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@blabla193: Noch ein paar Anmerkungen zum Quelltext:

Sternchen-Importe sind Böse™. Gerade bei `tkinter` holt man sich da über 100 Namen in das Modul von denen nur ein kleiner Bruchteil benötigt wird. Das macht es schwerer nachzuvollziehen welcher Name wo her kommt. Und es besteht die Gefahr von Namenskollisionen.

`locale`, `os`, und `sys` werden importiert, aber gar nicht verwendet.

`time` wird auch nicht wirklich gebraucht, das Du auch das `datetime`-Modul verwendest, was auch alles kann was Du da machst. Insbesondere ist es total unsinnig für Tag, Monat, und Jahr `time.strftime()` aufzurufen, aus den Zeichenketten ganze Zahlen zu machen, um daraus dann ein `date`-Objekt zu erstellen. Die haben eine `today()`-Methode die das aktuelle Datum als `date`-Objekt zur Verfügung stellen. Abgesehen davon, dass das so wie Du das machst sehr umständlich ist, gilt auch hier, dass die Zeit zwischen den drei Aufrufen ja fortschreitet, und sich deshalb nicht alle drei `time.strftime()`-Aufrufe auf den gleichen Tag beziehen müssen.

Für den Import von `date` und `datetime` aus `datetime` braucht man keine zwei ``import``-Anweisungen, das kann man in einer erledigen.

Konstanten schreibt man in Python KOMPLETT_GROSS.

Zu den Linienkommentaren hat Sirius3 ja schon etwas gesagt – ich würde da gerne noch hinzufügen das diese Zeilen mit 281 Zeichen auch viel zu lang sind. Man muss auch heute noch davon ausgehen, das es Umgebungen gibt die auf die traditionellen 80 Zeichen pro Zeile eingestellt sind.

Kommentare sollten dem Leser einen Mehrwert über den Code geben. Da sollte nicht stehen *was* gemacht wird, denn das steht da ja bereits als Code, sondern *warum* der Code das so macht. Sofern das nicht offensichtlich ist. Vieles wird offensichtlicher wenn man vernünftige Namen verwendet und keine komischen, kryptischen Abkürzungen. Wenn man `temperature` meint, sollte man nicht `temp` schreiben, was ziemlich oft auch für `temporary` steht, und damit für Verwirrung sorgen kann.

Man muss nicht jedes Zwischenergebnis an Namen binden, vor allen nicht wenn das sehr generische und dann auch noch durchnummerierte Namen sind. Überhaupt sollte man Namen nicht nummerieren. Das ist in der Regel ein Zeichen das man sich über die Namen mehr Gedanken machen sollte, oder gar keine einzelnen Namen möchte, sondern eine Datenstruktur. Oft eine Liste.

Bei den Bildern für Sonnenauf- und Untergang wird zum erstellen der Label mit dem jeweiligen Bild das gleiche gemacht. Solche Wiederholungen verletzen das DRY-Prinzip („Don't Repeat Yourself“). Hier würde sich eine Funktion anbieten.

Datei- und Schriftartnamen, sowie die URL der API würde ich als Konstanten definieren, damit da leicht Änderungen dran vornehmen kann ohne durch den gesamten Code gehen zu müssen.

Die Abfrage der Wetterdaten aus dem Internet und die Darstellung sollte man trennen, damit man die Abfrage einzeln testen kann, und auch leichter austauschen kann, wenn man den Anbieter für die Daten wechseln möchte oder muss.

Das Python-Listen von der Tk-Anbindung auf Tcl-Listen mit jeweils einem Leerzeichen zwischen jedem Element abgebildet werden, und die im Kontext von Zeichenketten den Inhalt dann als eine Zeichenkette darstellen, ist aus Python-Sicht ein wenig sehr ”magisch”, weil man da etwas über Tcl wissen muss um das zu verstehen, was man auch normal in Python durch Zeichenkettenformatierung ausdrücken kann, was dann Python-Programmierer besser/leichter verstehen.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from collections import namedtuple
from datetime import datetime as DateTime

import requests
from addict import Dict
from PIL import Image, ImageTk

OPEN_WEATHER_MAP_API_URL = (
    'http://api.openweathermap.org/data/2.5/weather'
    '?appid=...&q=tuebingen'
)
OPEN_WEATHER_MAP_DELAY = 30  # minutes.

SUNRISE_IMAGE_FILENAME = 'SAUFGANG.png'
SUNSET_IMAGE_FILENAME = 'SUNTERGANG.png'
BACKGROUND_COLOR = 'black'
FONT_NAME = 'Verdana'
WEEKDAY_NAMES = [
    'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag',
    'Freitag', 'Samstag', 'Sonntag'
]


Weather = namedtuple(
    'Weather', 'sunrise sunset temperature min_temperature max_temperature'
)


def convert_kelvin_to_celsius(value):
    return value - 273


def get_weather(url):
    data = Dict(requests.get(url).json())
    return Weather(
        DateTime.fromtimestamp(data.sys.sunrise),
        DateTime.fromtimestamp(data.sys.sunset),
        convert_kelvin_to_celsius(data.main.temp),
        convert_kelvin_to_celsius(data.main.temp_min),
        convert_kelvin_to_celsius(data.main.temp_max),
    )


def update_weather(
    sunrise_label,
    sunset_label,
    temperature_label,
    min_temperature_label,
    max_temperature_label,
):
    weather = get_weather(OPEN_WEATHER_MAP_API_URL)
    sunrise_label['text'] = format(weather.sunrise, '%H:%M Uhr')
    sunset_label['text'] = format(weather.sunset, '%H:%M Uhr')
    temperature_label['text'] = f'{weather.temperature:.1f} °C'
    min_temperature_label['text'] = f'min.: {weather.min_temperature:.1f} °C'
    max_temperature_label['text'] = f'max.: {weather.max_temperature:.1f} °C'
    sunrise_label.after(
        OPEN_WEATHER_MAP_DELAY * 60 * 1000,
        update_weather,
        sunrise_label,
        sunset_label,
        temperature_label,
        min_temperature_label,
        max_temperature_label,
    )


def create_image_label(master, filename):
    image = ImageTk.PhotoImage(
        Image.open(filename).resize((70, 70), Image.ANTIALIAS)
    )
    label = tk.Label(master, image=image, borderwidth=0)
    label.image = image
    return label


def update_date_and_time(time_label, seconds_label, weekday_label, date_label):
    now = DateTime.now()
    time_label['text'] = format(now, '%H:%M')
    seconds_label['text'] = format(now, '%S')
    weekday_label['text'] = WEEKDAY_NAMES[now.weekday()]
    date_label['text'] = format(now, '%d.%m.%Y')
    time_label.after(
        500,
        update_date_and_time,
        time_label,
        seconds_label,
        weekday_label,
        date_label
    )


def main():
    root = tk.Tk()
    root.attributes('-fullscreen', True)
    root.config(background=BACKGROUND_COLOR)

    create_image_label(root, SUNRISE_IMAGE_FILENAME).place(x=830, y=670)
    create_image_label(root, SUNSET_IMAGE_FILENAME).place(x=830, y=740)

    time_label = tk.Label(
        root, font=(FONT_NAME, 100), bg=BACKGROUND_COLOR, fg='white'
    )
    time_label.place(x=550, y=63)
    seconds_label = tk.Label(
        root, font=(FONT_NAME, 60), bg=BACKGROUND_COLOR, fg='grey'
    )
    seconds_label.place(x=980, y=71)
    weekday_label = tk.Label(
        root, font=(FONT_NAME, 40), bg=BACKGROUND_COLOR, fg='white'
    )
    weekday_label.place(x=0, y=650)
    date_label = tk.Label(
        root, font=(FONT_NAME, 50), bg=BACKGROUND_COLOR, fg='white'
    )
    date_label.place(x=0, y=740)

    update_date_and_time(time_label, seconds_label, weekday_label, date_label)


    sunrise_label = tk.Label(
        root, font=(FONT_NAME, 30), bg=BACKGROUND_COLOR, fg='white'
    )
    sunrise_label.place(x=900, y=680)
    sunset_label = tk.Label(
        root, font=(FONT_NAME, 30), bg=BACKGROUND_COLOR, fg='white'
    )
    sunset_label.place(x=900, y=750)
    temperature_label = tk.Label(
        root, font=(FONT_NAME, 80), bg=BACKGROUND_COLOR, fg='white'
    )
    temperature_label.place(x=890, y=500)
    min_temperature_label = tk.Label(
        root, font=(FONT_NAME, 30), bg=BACKGROUND_COLOR, fg='white'
    )
    min_temperature_label.place(x=1200, y=680)
    max_temperature_label = tk.Label(
        root, font=(FONT_NAME, 30), bg=BACKGROUND_COLOR, fg='white'
    )
    max_temperature_label.place(x=1200, y=750)
    
    update_weather(
        sunrise_label,
        sunset_label,
        temperature_label,
        min_temperature_label,
        max_temperature_label,
    )
    root.mainloop()


if __name__ == '__main__':
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten