目录
Python实例题
题目
基于 Python 的简单天气查询应用
要求:
- 使用 Python 构建一个天气查询应用,支持以下功能:
- 通过城市名称查询实时天气信息
- 显示天气状况、温度、湿度、风速等基本信息
- 支持多日天气预报查询
- 显示天气趋势图表(温度变化、降水概率等)
- 支持摄氏度 / 华氏度单位切换
- 使用
tkinter
构建图形用户界面。 - 调用公开天气 API 获取数据(如 OpenWeatherMap)。
解题思路:
- 使用
tkinter
构建界面,包括输入框、查询按钮和结果显示区域。 - 通过
requests
库调用 OpenWeatherMap API 获取天气数据。 - 使用
matplotlib
绘制天气趋势图表。
代码实现:
import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import requests
import json
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import datetime
import threading
import time
# 设置中文字体
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
class WeatherApp:
def __init__(self, root):
self.root = root
self.root.title("天气查询应用")
self.root.geometry("800x600")
# OpenWeatherMap API密钥(需要替换为自己的API密钥)
self.api_key = "YOUR_API_KEY"
self.base_url = "http://api.openweathermap.org/data/2.5/"
# 温度单位(C或F)
self.temp_unit = "C"
# 创建主界面
self.create_main_window()
def create_main_window(self):
"""创建主窗口"""
# 创建顶部框架 - 搜索区域
top_frame = ttk.Frame(self.root, padding=10)
top_frame.pack(fill=tk.X)
# 城市输入框
ttk.Label(top_frame, text="城市:").pack(side=tk.LEFT, padx=5)
self.city_var = tk.StringVar()
city_entry = ttk.Entry(top_frame, textvariable=self.city_var, width=30)
city_entry.pack(side=tk.LEFT, padx=5)
city_entry.bind("<Return>", lambda event: self.get_weather())
# 查询按钮
ttk.Button(top_frame, text="查询", command=self.get_weather).pack(side=tk.LEFT, padx=5)
# 单位切换按钮
self.unit_var = tk.StringVar(value="摄氏度")
unit_cb = ttk.Combobox(top_frame, textvariable=self.unit_var, values=["摄氏度", "华氏度"], width=8)
unit_cb.pack(side=tk.LEFT, padx=5)
unit_cb.bind("<<ComboboxSelected>>", self.change_unit)
# 创建主框架 - 结果显示区域
main_frame = ttk.Frame(self.root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)
# 创建左侧面板 - 当前天气信息
left_frame = ttk.LabelFrame(main_frame, text="当前天气", padding=10)
left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 城市和日期
self.city_date_var = tk.StringVar(value="")
ttk.Label(left_frame, textvariable=self.city_date_var, font=('SimHei', 16, 'bold')).pack(anchor=tk.W)
# 天气图标
self.weather_icon = tk.Label(left_frame, text="☀️", font=('SimHei', 48))
self.weather_icon.pack(pady=10)
# 温度
self.temp_var = tk.StringVar(value="")
ttk.Label(left_frame, textvariable=self.temp_var, font=('SimHei', 24)).pack(pady=5)
# 天气描述
self.desc_var = tk.StringVar(value="")
ttk.Label(left_frame, textvariable=self.desc_var, font=('SimHei', 14)).pack(pady=5)
# 其他信息
self.other_info = scrolledtext.ScrolledText(left_frame, width=30, height=10, wrap=tk.WORD)
self.other_info.pack(fill=tk.BOTH, expand=True, pady=10)
# 创建右侧面板 - 预报信息
right_frame = ttk.LabelFrame(main_frame, text="天气预报", padding=10)
right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 创建图表框架
self.chart_frame = ttk.Frame(right_frame)
self.chart_frame.pack(fill=tk.BOTH, expand=True)
# 创建预报信息框架
self.forecast_frame = ttk.Frame(right_frame)
self.forecast_frame.pack(fill=tk.X, pady=10)
# 创建状态栏
self.status_var = tk.StringVar(value="就绪")
ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(side=tk.BOTTOM, fill=tk.X)
def get_weather(self):
"""获取天气信息"""
city = self.city_var.get().strip()
if not city:
messagebox.showinfo("提示", "请输入城市名称")
return
# 清空之前的结果
self.other_info.delete(1.0, tk.END)
# 在单独的线程中获取天气数据
self.status_var.set(f"正在查询 {city} 的天气...")
threading.Thread(target=self._get_weather_thread, args=(city,)).start()
def _get_weather_thread(self, city):
"""在单独的线程中获取天气数据"""
try:
# 获取当前天气
current_weather_url = f"{self.base_url}weather?q={city}&appid={self.api_key}&units=metric"
current_response = requests.get(current_weather_url)
current_data = json.loads(current_response.text)
if current_response.status_code != 200:
error_msg = current_data.get('message', '未知错误')
self.root.after(0, lambda: messagebox.showerror("错误", f"获取天气失败: {error_msg}"))
self.root.after(0, lambda: self.status_var.set("就绪"))
return
# 获取预报数据
forecast_url = f"{self.base_url}forecast?q={city}&appid={self.api_key}&units=metric"
forecast_response = requests.get(forecast_url)
forecast_data = json.loads(forecast_response.text)
if forecast_response.status_code != 200:
error_msg = forecast_data.get('message', '未知错误')
self.root.after(0, lambda: messagebox.showerror("错误", f"获取预报失败: {error_msg}"))
self.root.after(0, lambda: self.status_var.set("就绪"))
return
# 更新UI
self.root.after(0, lambda: self._update_ui(current_data, forecast_data))
except Exception as e:
self.root.after(0, lambda: messagebox.showerror("错误", f"发生异常: {str(e)}"))
finally:
self.root.after(0, lambda: self.status_var.set("就绪"))
def _update_ui(self, current_data, forecast_data):
"""更新UI显示"""
# 更新城市和日期
city_name = current_data['name']
country = current_data['sys']['country']
current_time = datetime.datetime.fromtimestamp(current_data['dt']).strftime('%Y-%m-%d %H:%M:%S')
self.city_date_var.set(f"{city_name}, {country} - {current_time}")
# 更新天气图标和描述
weather = current_data['weather'][0]
weather_desc = weather['description'].capitalize()
self.desc_var.set(weather_desc)
# 设置天气图标(使用表情符号)
weather_id = weather['id']
if weather_id < 300: # 雷雨
self.weather_icon.config(text="⛈️")
elif weather_id < 600: # 雨
self.weather_icon.config(text="🌧️")
elif weather_id < 700: # 雪
self.weather_icon.config(text="❄️")
elif weather_id < 800: # 雾/霾
self.weather_icon.config(text="🌫️")
elif weather_id == 800: # 晴天
self.weather_icon.config(text="☀️")
elif weather_id <= 804: # 多云
self.weather_icon.config(text="☁️")
else:
self.weather_icon.config(text="❓")
# 更新温度
temp = current_data['main']['temp']
feels_like = current_data['main']['feels_like']
temp_min = current_data['main']['temp_min']
temp_max = current_data['main']['temp_max']
if self.temp_unit == "F":
temp = self.celsius_to_fahrenheit(temp)
feels_like = self.celsius_to_fahrenheit(feels_like)
temp_min = self.celsius_to_fahrenheit(temp_min)
temp_max = self.celsius_to_fahrenheit(temp_max)
temp_text = f"{temp:.1f}°{'C' if self.temp_unit == 'C' else 'F'}"
self.temp_var.set(temp_text)
# 更新其他信息
other_info_text = (
f"体感温度: {feels_like:.1f}°{'C' if self.temp_unit == 'C' else 'F'}\n"
f"最低温度: {temp_min:.1f}°{'C' if self.temp_unit == 'C' else 'F'}\n"
f"最高温度: {temp_max:.1f}°{'C' if self.temp_unit == 'C' else 'F'}\n"
f"湿度: {current_data['main']['humidity']}%\n"
f"气压: {current_data['main']['pressure']} hPa\n"
f"风速: {current_data['wind']['speed']} m/s\n"
f"能见度: {current_data.get('visibility', 'N/A')} m"
)
self.other_info.delete(1.0, tk.END)
self.other_info.insert(tk.END, other_info_text)
# 绘制温度趋势图表
self._plot_temperature_trend(forecast_data)
# 显示未来几天的预报
self._show_forecast(forecast_data)
def _plot_temperature_trend(self, forecast_data):
"""绘制温度趋势图表"""
# 清除之前的图表
for widget in self.chart_frame.winfo_children():
widget.destroy()
# 准备数据
dates = []
temps = []
temp_mins = []
temp_maxs = []
for item in forecast_data['list'][:8]: # 显示未来8个时段(约3天)
date = datetime.datetime.fromtimestamp(item['dt']).strftime('%m-%d %H:%M')
dates.append(date)
temp = item['main']['temp']
temp_min = item['main']['temp_min']
temp_max = item['main']['temp_max']
if self.temp_unit == "F":
temp = self.celsius_to_fahrenheit(temp)
temp_min = self.celsius_to_fahrenheit(temp_min)
temp_max = self.celsius_to_fahrenheit(temp_max)
temps.append(temp)
temp_mins.append(temp_min)
temp_maxs.append(temp_max)
# 创建图表
fig, ax = plt.subplots(figsize=(8, 4))
ax.plot(dates, temps, 'o-', label='温度')
ax.plot(dates, temp_mins, '^-', label='最低温度')
ax.plot(dates, temp_maxs, 'v-', label='最高温度')
# 设置图表属性
ax.set_title('温度趋势')
ax.set_xlabel('日期和时间')
ax.set_ylabel(f'温度 (°{"C" if self.temp_unit == "C" else "F"})')
ax.legend()
plt.xticks(rotation=45)
plt.tight_layout()
# 将图表嵌入到Tkinter窗口
canvas = FigureCanvasTkAgg(fig, master=self.chart_frame)
canvas.draw()
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
def _show_forecast(self, forecast_data):
"""显示未来几天的预报"""
# 清除之前的预报
for widget in self.forecast_frame.winfo_children():
widget.destroy()
# 按天分组预报数据
daily_forecasts = {}
for item in forecast_data['list']:
date = datetime.datetime.fromtimestamp(item['dt']).strftime('%Y-%m-%d')
if date not in daily_forecasts:
daily_forecasts[date] = []
daily_forecasts[date].append(item)
# 只保留每天的中午预报(约12:00)
daily_representatives = []
for date, items in daily_forecasts.items():
# 找到最接近中午的预报
closest_item = min(items, key=lambda x: abs(12 - datetime.datetime.fromtimestamp(x['dt']).hour))
daily_representatives.append(closest_item)
# 显示未来3天的预报
for i, item in enumerate(daily_representatives[:3]):
day_frame = ttk.Frame(self.forecast_frame, relief=tk.RAISED, borderwidth=1)
day_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
# 日期
date = datetime.datetime.fromtimestamp(item['dt']).strftime('%m-%d')
ttk.Label(day_frame, text=date, font=('SimHei', 12, 'bold')).pack(pady=5)
# 天气图标
weather_id = item['weather'][0]['id']
if weather_id < 300: # 雷雨
icon = "⛈️"
elif weather_id < 600: # 雨
icon = "🌧️"
elif weather_id < 700: # 雪
icon = "❄️"
elif weather_id < 800: # 雾/霾
icon = "🌫️"
elif weather_id == 800: # 晴天
icon = "☀️"
elif weather_id <= 804: # 多云
icon = "☁️"
else:
icon = "❓"
ttk.Label(day_frame, text=icon, font=('SimHei', 24)).pack(pady=5)
# 温度
temp_min = item['main']['temp_min']
temp_max = item['main']['temp_max']
if self.temp_unit == "F":
temp_min = self.celsius_to_fahrenheit(temp_min)
temp_max = self.celsius_to_fahrenheit(temp_max)
ttk.Label(day_frame, text=f"{temp_min:.1f}° - {temp_max:.1f}°{'C' if self.temp_unit == 'C' else 'F'}").pack(pady=5)
# 天气描述
desc = item['weather'][0]['description'].capitalize()
ttk.Label(day_frame, text=desc).pack(pady=5)
def change_unit(self, event=None):
"""切换温度单位"""
new_unit = self.unit_var.get()
self.temp_unit = "C" if new_unit == "摄氏度" else "F"
# 如果已有天气数据,刷新显示
if self.city_date_var.get():
city = self.city_var.get().strip()
if city:
self.get_weather()
def celsius_to_fahrenheit(self, celsius):
"""将摄氏度转换为华氏度"""
return (celsius * 9/5) + 32
if __name__ == "__main__":
root = tk.Tk()
app = WeatherApp(root)
root.mainloop()