HiveBrain v1.2.0
Get Started
← Back to all entries
snippetpythonMinor

Create a weather app in Python

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
pythonappcreateweather

Problem

I've created the following app in Python 3.5. The app uses pyowm and will take a location and return weather information. I've also built a GUI with tkinter for it.

Keen to find out some tips to improve my coding style.

```
import tkinter as tk
import pyowm
import datetime, time

class WeatherInfo(tk.Tk):

def __init__(self):
tk.Tk.__init__(self)
self.wm_title('Weather')
self.temp = tk.StringVar(self,value='')
self.humid = tk.StringVar(self,value='')
self.status = tk.StringVar(self,value='')
self.sunrise = tk.StringVar(self,value='')
self.sunset = tk.StringVar(self,value='')
self.ld = tk.StringVar(self,value = '')
self.ln = tk.StringVar(self,value = '')

self.ask = tk.LabelFrame(self, text ='Location')
self.ask.pack(fill='both',expand='yes')
self.kw_label = tk.Label(self.ask, text ='Get weather in:')
self.kw_label.pack(side = tk.LEFT)
self.kw = tk.Entry(self.ask)
self.kw.pack(side = tk.RIGHT)
self.rb = tk.Button(self, text='Go', command = self.search)
self.rb.pack()
self.owm = pyowm.OWM('CENSORED')

self.output = tk.LabelFrame(self, text ='Information')
self.output.pack(fill='both',expand='yes')
tk.Label(self.output, textvariable = self.temp).pack()
tk.Label(self.output, textvariable=self.humid).pack()
tk.Label(self.output, textvariable=self.status).pack()
tk.Label(self.output, textvariable=self.sunrise).pack()
tk.Label(self.output, textvariable=self.sunset).pack()
tk.Label(self.output, textvariable=self.ld).pack()
tk.Label(self.output, textvariable=self.ln).pack()
button = tk.Button(master=self, text='Quit', command=self._quit)
button.pack(side=tk.BOTTOM)

def search(self):
obs = self.owm.weather_at_place(self.kw.get())
try:
w = obs.get_weather()
self.temp.set('Temperature: ' +str(round

Solution

Your code has a few different responsibilities, most of which are currently in the search function:

  • Setting up the GUI



  • Retrieving the weather report



  • Parsing the relevant data out of that report



  • Displaying the result



Each of these deserves their own function. I would also abstract the format itself away into a constant of the class. This allows looping over the different keys to build the objects instead of having to do it manually. I used a collections.OrderedDict to preserve the order of them.

These template strings are:

class WeatherInfo(tk.Tk):
    templates = OrderedDict([
        ('temp', 'Temperature: {temp:.1f} C'),
        ('humid', 'Humidity: {humid}%'),
        ('status', 'Status: {status}'),
        ('sunrise', 'Sunrise at {sunrise:%H:%M:%S}'),
        ('sunset', 'Sunset at {sunset:%H:%M:%S}'),
        ('day_length', 'Day length: {day_length:.1f} h'),
        ('night_length', 'Night length: {night_length:.1f} h')])


Note that you can already define the format for the date in there and also the rounding for floats.

This allows doing this in __init__:

def __init__(self):
    ...
    self.tk_info = {key: tk.StringVar(
        self, value='') for key in WeatherInfo.templates}
    ...
    self.labels = []
    for key in WeatherInfo.templates:
        self.labels.append(
            tk.Label(self.output, textvariable=self.tk_info[key]).pack())
    ...


search should just be concerned with getting the result from the web, catching an error if needed. Here you should notice that you should never have a bare except. It will also catch e.g. the user pressing CTRL-C to abort the program. Always use at least except Exception which will catch almost everything (and not some special exceptions like CTRL-C). Here you can be more specific, because the code will throw a AttributeError if unsuccessful:

def search(self):
    obs = self.owm.weather_at_place(self.kw.get())
    try:
        return json.loads(obs.get_weather().to_JSON())
    except AttributeError:
        self.tk_info['temp'].set('Pick a city to display weather.')


Note that I am using the json format here, just because I think it is more intuitive to work with. But you can also use the get_* functions in parse.

Speaking of which: I would add a parse function that takes the result of obs.get_weather and does all the conversions/calculations. It returns a dictionary with the raw values we want to put in the template:

def parse(self, w):
    parsed_weather = {'temp': round(w['temperature']['temp'] - 273.15, 0),
                      'humid': w['humidity'],
                      'status': w['status'],
                      'sunrise': datetime.fromtimestamp(w['sunrise_time']),
                      'sunset': datetime.fromtimestamp(w['sunset_time']),
                      'day_length': round((w['sunset_time'] - w['sunrise_time']) / 3600, 1),
                      'night_length': round(24 - (w['sunset_time'] - w['sunrise_time']) / 3600, 1)}
    return parsed_weather


I also used from datetime import datetime in the header to get rid of one level of redundant naming.

Then I would define a update function, which updates all the labels to the correct wording. Here it comes in handy that we already have the template strings defined and have a dictionary of the values we want to put in there!

def update(self, report):
    for key, template in WeatherInfo.templates.items():
        self.tk_info[key].set(template.format(**report))


The whole thing comes together in the main function (for lack of a better word):

def main(self):
    report = self.search()
    if report:
        self.update(self.parse(report))


Of course, this main function has to be linked to the Go button instead of search now.

Finally, I would guard the execution of the app with a if __name__ == "__main__" clause to allow importing of your module without executing it.

Final code:

```
import tkinter as tk
import pyowm
import time
import json
from datetime import datetime
from collections import OrderedDict

class WeatherInfo(tk.Tk):
templates = OrderedDict([
('temp', 'Temperature: {temp:.1f} C'),
('humid', 'Humidity: {humid}%'),
('status', 'Status: {status}'),
('sunrise', 'Sunrise at {sunrise:%H:%M:%S}'),
('sunset', 'Sunset at {sunset:%H:%M:%S}'),
('day_length', 'Day length: {day_length:.1f} h'),
('night_length', 'Night length: {night_length:.1f} h')])

def __init__(self):
tk.Tk.__init__(self)
self.wm_title('Weather')
self.tk_info = {key: tk.StringVar(
self, value='') for key in WeatherInfo.templates}

self.ask = tk.LabelFrame(self, text='Location')
self.ask.pack(fill='both', expand='yes')
self.kw_label = tk.Label(self.ask, text='City:')
self.kw_label.pack(side=tk.LEFT)
self.kw = tk.Entry(self.ask)
self.kw.pack(side=tk.LEFT)
self.rb = tk.Bu

Code Snippets

class WeatherInfo(tk.Tk):
    templates = OrderedDict([
        ('temp', 'Temperature: {temp:.1f} C'),
        ('humid', 'Humidity: {humid}%'),
        ('status', 'Status: {status}'),
        ('sunrise', 'Sunrise at {sunrise:%H:%M:%S}'),
        ('sunset', 'Sunset at {sunset:%H:%M:%S}'),
        ('day_length', 'Day length: {day_length:.1f} h'),
        ('night_length', 'Night length: {night_length:.1f} h')])
def __init__(self):
    ...
    self.tk_info = {key: tk.StringVar(
        self, value='') for key in WeatherInfo.templates}
    ...
    self.labels = []
    for key in WeatherInfo.templates:
        self.labels.append(
            tk.Label(self.output, textvariable=self.tk_info[key]).pack())
    ...
def search(self):
    obs = self.owm.weather_at_place(self.kw.get())
    try:
        return json.loads(obs.get_weather().to_JSON())
    except AttributeError:
        self.tk_info['temp'].set('Pick a city to display weather.')
def parse(self, w):
    parsed_weather = {'temp': round(w['temperature']['temp'] - 273.15, 0),
                      'humid': w['humidity'],
                      'status': w['status'],
                      'sunrise': datetime.fromtimestamp(w['sunrise_time']),
                      'sunset': datetime.fromtimestamp(w['sunset_time']),
                      'day_length': round((w['sunset_time'] - w['sunrise_time']) / 3600, 1),
                      'night_length': round(24 - (w['sunset_time'] - w['sunrise_time']) / 3600, 1)}
    return parsed_weather
def update(self, report):
    for key, template in WeatherInfo.templates.items():
        self.tk_info[key].set(template.format(**report))

Context

StackExchange Code Review Q#141279, answer score: 2

Revisions (0)

No revisions yet.