投稿/コメントを表示します。

イトケンです。課題提出します。
前回は、お天気アプリを作成し、メインフォームに4都市(①横浜、②大阪、③広島、④山口)の
ラジオボタンを設置し、天気を知りたい都市をラジオボタンで選択し、結果を表示するアプリを
作成しました。

今回は、前回作成したコードを基に日本全国の政令指定都市20都市について天気を調べられるように
改変しました。具体的には、20都市分のチェックボックスを配置し、チェックした都市の天気を
調べられるようにしました。

講座ではthreadを用いた並列処理で作成するよう指示がありましたが、まず、順次処理で作成してみました。
そして次に同時並行版を作成し実行結果を比較してみました。

◆日本の政令指定都市お天気調査【順次処理】アプリ
"""
政令指定都市天気アプリ - 順次処理版(threadingなし)
"""
import tkinter as tk
from tkinter import ttk, scrolledtext
import urllib.request
import json
from datetime import datetime


class WeatherApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("政令指定都市天気アプリ(順次処理版)")
        self.geometry("700x600")

        # 政令指定都市(英語名マッピング)
        self.cities = {
            "札幌市": "Sapporo", "仙台市": "Sendai", "さいたま市": "Saitama",
            "千葉市": "Chiba", "横浜市": "Yokohama", "川崎市": "Kawasaki",
            "相模原市": "Sagamihara", "新潟市": "Niigata", "静岡市": "Shizuoka",
            "浜松市": "Hamamatsu", "名古屋市": "Nagoya", "京都市": "Kyoto",
            "大阪市": "Osaka", "堺市": "Sakai", "神戸市": "Kobe",
            "岡山市": "Okayama", "広島市": "Hiroshima", "北九州市": "Kitakyushu",
            "福岡市": "Fukuoka", "熊本市": "Kumamoto"
        }

        self.city_vars = {city: tk.BooleanVar() for city in self.cities}

        # 英語→日本語変換辞書
        self.weather_dict = {
            "Sunny": "晴れ", "Clear": "快晴", "Partly cloudy": "曇り時々晴れ",
            "Cloudy": "曇り", "Overcast": "曇り", "Mist": "霧", "Fog": "霧",
            "Light rain": "小雨", "Moderate rain": "雨", "Heavy rain": "大雨",
            "Light snow": "小雪", "Moderate snow": "雪", "Heavy snow": "大雪",
            "Thundery outbreaks possible": "雷雨の可能性", "Patchy rain possible": "にわか雨の可能性",
            "Light drizzle": "霧雨", "Heavy drizzle": "強い霧雨",
            "Patchy rain nearby": "近くで雨", "Shower in vicinity": "近くでにわか雨",
            "Patchy light rain": "薄い小雨", "Thunderstorm": "雷雨",
            "Thunderstorm, shower in vicinity": "雷雨、近くでにわか雨",
            "Parftial fog": "部分的に霧", "Partial fog": "部分的に霧"
        }

        self.create_widgets()

    def create_widgets(self):
        # 都市選択
        frame1 = ttk.Frame(self)
        frame1.pack(fill=tk.X, padx=10, pady=5)
        ttk.Label(frame1, text="都市選択:", font=("Arial", 12)).pack(anchor=tk.W)

        # チェックボックス(4列)
        cb_frame = ttk.Frame(frame1)
        cb_frame.pack(fill=tk.X, pady=5)
        for i, city in enumerate(self.cities):
            ttk.Checkbutton(cb_frame, text=city, variable=self.city_vars[city]).grid(
                row=i // 4, column=i % 4, sticky=tk.W, padx=15)

        # ボタン
        btn_frame = ttk.Frame(self)
        btn_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(btn_frame, text="全選択", command=self.select_all).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="全解除", command=self.clear_all).pack(side=tk.LEFT, padx=5)
        self.fetch_button = ttk.Button(btn_frame, text="天気取得", command=self.get_weather)
        self.fetch_button.pack(side=tk.RIGHT, padx=5)

        # 進行状況表示
        self.status_label = ttk.Label(self, text="準備完了", font=("Arial", 10))
        self.status_label.pack(anchor=tk.W, padx=10)

        # 結果表示
        ttk.Label(self, text="結果:", font=("Arial", 12)).pack(anchor=tk.W, padx=10)
        self.result_text = scrolledtext.ScrolledText(self, height=25, font=("Consolas", 9))
        self.result_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

    def select_all(self):
        for var in self.city_vars.values():
            var.set(True)

    def clear_all(self):
        for var in self.city_vars.values():
            var.set(False)

    def get_weather(self):
        selected = [city for city, var in self.city_vars.items() if var.get()]
        if not selected:
            self.result_text.insert(tk.END, "都市を選択してください\n")
            return

        # ボタン無効化
        self.fetch_button.config(state='disabled')

        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, f"順次取得開始... ({len(selected)}都市)\n")
        self.result_text.insert(tk.END, "=" * 50 + "\n\n")
        self.result_text.update()

        results = {}

        # 順次処理(1都市ずつ処理)
        for i, city in enumerate(selected, 1):
            self.status_label.config(text=f"取得中: {city} ({i}/{len(selected)})")
            self.update()  # UI更新

            try:
                api_city = self.cities[city]
                url = f"https://wttr.in/{api_city}?format=j1"
                with urllib.request.urlopen(url, timeout=10) as response:
                    data = json.loads(response.read().decode('utf-8'))
                    current = data['current_condition'][0]
                    today = data['weather'][0]

                    # 天気を日本語に変換
                    weather_en = current['weatherDesc'][0]['value']
                    weather_jp = self.weather_dict.get(weather_en, weather_en)

                    result = f"""{city}
天気: {weather_jp}
気温: {current['temp_C']}℃ (最高{today['maxtempC']}℃/最低{today['mintempC']}℃)
湿度: {current['humidity']}% 風速: {current['windspeedKmph']}km/h

"""
                    results[city] = result

                    # リアルタイム表示
                    self.result_text.insert(tk.END, result)
                    self.result_text.see(tk.END)
                    self.result_text.update()

            except Exception as e:
                error_msg = f"{city}: エラー - {str(e)}\n\n"
                results[city] = error_msg
                self.result_text.insert(tk.END, error_msg)
                self.result_text.see(tk.END)
                self.result_text.update()

        # 完了処理
        self.result_text.insert(tk.END, f"順次取得完了: {len(results)}都市")
        self.status_label.config(text=f"取得完了 - {datetime.now().strftime('%H:%M:%S')}")
        self.fetch_button.config(state='normal')


if __name__ == "__main__":
    WeatherApp().mainloop()

チェックボックスで選択した都市の天気が1都市ずつ順次表示されました。
次にthreadを用いて同時並行処理するよう改変しました。

◆日本の政令指定都市お天気調査【並列処理】アプリ
"""
政令指定都市天気アプリ - thread並行処理
"""
import tkinter as tk
from tkinter import ttk, scrolledtext
import threading
import urllib.request
import json
from datetime import datetime


class WeatherApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("政令指定都市天気アプリ")
        self.geometry("700x600")

        # 政令指定都市(英語名マッピング)
        self.cities = {
            "札幌市": "Sapporo", "仙台市": "Sendai", "さいたま市": "Saitama",
            "千葉市": "Chiba", "横浜市": "Yokohama", "川崎市": "Kawasaki",
            "相模原市": "Sagamihara", "新潟市": "Niigata", "静岡市": "Shizuoka",
            "浜松市": "Hamamatsu", "名古屋市": "Nagoya", "京都市": "Kyoto",
            "大阪市": "Osaka", "堺市": "Sakai", "神戸市": "Kobe",
            "岡山市": "Okayama", "広島市": "Hiroshima", "北九州市": "Kitakyushu",
            "福岡市": "Fukuoka", "熊本市": "Kumamoto"
        }

        self.city_vars = {city: tk.BooleanVar() for city in self.cities}
        self.results = {}

        # 英語→日本語変換辞書
        self.weather_dict = {
            "Sunny": "晴れ", "Clear": "快晴", "Partly cloudy": "曇り時々晴れ",
            "Cloudy": "曇り", "Overcast": "曇り", "Mist": "霧", "Fog": "霧",
            "Light rain": "小雨", "Moderate rain": "雨", "Heavy rain": "大雨",
            "Light snow": "小雪", "Moderate snow": "雪", "Heavy snow": "大雪",
            "Thundery outbreaks possible": "雷雨の可能性", "Patchy rain possible": "にわか雨の可能性",
            "Light drizzle": "霧雨", "Heavy drizzle": "強い霧雨",
            "Patchy rain nearby": "近くで雨", "Shower in vicinity": "近くでにわか雨",
            "Patchy light rain": "薄い小雨", "Thunderstorm": "雷雨",
            "Thunderstorm, shower in vicinity": "雷雨、近くでにわか雨",
            "Parftial fog": "部分的に霧", "Partial fog": "部分的に霧"
        }

        self.create_widgets()

    def create_widgets(self):
        # 都市選択
        frame1 = ttk.Frame(self)
        frame1.pack(fill=tk.X, padx=10, pady=5)
        ttk.Label(frame1, text="都市選択:", font=("Arial", 12)).pack(anchor=tk.W)

        # チェックボックス(4列)
        cb_frame = ttk.Frame(frame1)
        cb_frame.pack(fill=tk.X, pady=5)
        for i, city in enumerate(self.cities):
            ttk.Checkbutton(cb_frame, text=city, variable=self.city_vars[city]).grid(
                row=i // 4, column=i % 4, sticky=tk.W, padx=15)

        # ボタン
        btn_frame = ttk.Frame(self)
        btn_frame.pack(fill=tk.X, padx=10, pady=5)
        ttk.Button(btn_frame, text="全選択", command=self.select_all).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="全解除", command=self.clear_all).pack(side=tk.LEFT, padx=5)
        ttk.Button(btn_frame, text="天気取得", command=self.get_weather).pack(side=tk.RIGHT, padx=5)

        # 結果表示
        ttk.Label(self, text="結果:", font=("Arial", 12)).pack(anchor=tk.W, padx=10)
        self.result_text = scrolledtext.ScrolledText(self, height=25, font=("Consolas", 9))
        self.result_text.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

    def select_all(self):
        for var in self.city_vars.values():
            var.set(True)

    def clear_all(self):
        for var in self.city_vars.values():
            var.set(False)

    def get_weather(self):
        selected = [city for city, var in self.city_vars.items() if var.get()]
        if not selected:
            self.result_text.insert(tk.END, "都市を選択してください\n")
            return

        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, f"取得中... ({len(selected)}都市)\n{'=' * 50}\n")
        self.results.clear()

        # 並行処理
        threads = []
        for city in selected:
            thread = threading.Thread(target=self.fetch_weather, args=(city,))
            threads.append(thread)
            thread.start()

        # 完了待機
        threading.Thread(target=self.wait_results, args=(threads,)).start()

    def fetch_weather(self, city):
        try:
            api_city = self.cities[city]
            url = f"https://wttr.in/{api_city}?format=j1"
            with urllib.request.urlopen(url, timeout=10) as response:
                data = json.loads(response.read().decode('utf-8'))
                current = data['current_condition'][0]
                today = data['weather'][0]

                # 天気を日本語に変換
                weather_en = current['weatherDesc'][0]['value']
                weather_jp = self.weather_dict.get(weather_en, weather_en)

                self.results[city] = f"""{city}
天気: {weather_jp}
気温: {current['temp_C']}℃ (最高{today['maxtempC']}℃/最低{today['mintempC']}℃)
湿度: {current['humidity']}% 風速: {current['windspeedKmph']}km/h
"""
        except Exception as e:
            self.results[city] = f"{city}: エラー - {str(e)}\n"

    def wait_results(self, threads):
        for thread in threads:
            thread.join()
        self.after(0, self.display_results)

    def display_results(self):
        self.result_text.delete(1.0, tk.END)
        self.result_text.insert(tk.END, f"天気情報 - {datetime.now().strftime('%Y/%m/%d %H:%M')}\n")
        self.result_text.insert(tk.END, "=" * 50 + "\n\n")

        for city, result in self.results.items():
            self.result_text.insert(tk.END, result + "\n")

        self.result_text.insert(tk.END, f"取得完了: {len(self.results)}都市")


if __name__ == "__main__":
    WeatherApp().mainloop()

取り急ぎ、ご報告いたします。やったことは前回の宿題をベースにAI(クロード)に
・日本の政令指定都市(20か所)の天気を調査するフォームに改変
・各政令指定都市にチェックボックスを設置して調べたい都市を選択できる
・threadを使って、同時並行処理する
と指示しただけです。

threadを使って、同時並行処理すると20都市のお天気がバッと同時表示されるので気持ちイイですね(^▽^)/
2025/08/17 21:02