snippetpythonMinor
Create a weather app in Python
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
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
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
These template strings are:
Note that you can already define the format for the date in there and also the rounding for floats.
This allows doing this in
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
Speaking of which: I would add a
I also used
Then I would define a
The whole thing comes together in the
Of course, this main function has to be linked to the
Finally, I would guard the execution of the app with a
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
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_weatherI 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_weatherdef 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.