Python for Beginners

Flet 날씨 App

Andrew's Akashic Records 2024. 11. 5. 10:51
728x90

날씨 앱 코드 소스

이 책에 포함된 날씨 어플리케이션은 flet를 이용해 UI를 구성하고, python_weather 라이브러리로 날씨 데이터를 가져옵니다. 이 앱은 사용자가 특정 도시를 입력하면 해당 도시의 날씨 상태(아이콘, 온도, 습도 등)를 표시합니다.

비동기 처리를 통해 get_weather 함수가 날씨 데이터를 효율적으로 가져올 수 있도록 설계되었으며, flet의 다양한 UI 컴포넌트를 사용하여 간단하면서도 유용한 GUI를 구성하고 있습니다.

import flet as ft
import python_weather


# Functions
def get_weather_icon(condition):
    if condition == "Thunderstorm":
        return '🌩️'
    elif condition == "Drizzle":
        return '🌧️'
    elif condition == "Rainy":
        return '☔'
    elif condition == "Snowy":
        return '☃️'
    elif condition == "Windy":
        return '🌀'
    elif condition == "Sunny":
        return '☀️'
    elif condition == "Cloudy":
        return '☁️'
    elif condition == "Clear":
        return '🌝'
    else:
        return '😁'


def to_celsius(fa):
    changeNumber = (int(fa) - 32) * 5.0 / 9.0
    return round(changeNumber, 1)


def main(page: ft.Page):
    async def get_weather(e):
        async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
            weater = await  client.get(inputBox.value)
            temperText.value = str(to_celsius(weater.temperature)) + ' ° '
            humidityPercent.value = '💧 ' + str(weater.humidity) + '%'
            cityName.value = inputBox.value
            iconImage.value = get_weather_icon((weater.description))
            weather_text.value = weater.description
            page.update()

    page.title = "Weather Layout"
    page.window.width = 300
    page.window.height = 350
    page.horizontal_alignment = page.vertical_alignment = "center"

    cityName = ft.ElevatedButton('seoul')
    humidityPercent = ft.Text('💧 22%',
                              color="blueaccent",
                              size=20,
                              weight=ft.FontWeight.BOLD)
    weather_city = ft.Container(content=ft.Row([
        cityName,
        humidityPercent],
        alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
        margin=10)

    iconImage = ft.Text('☀️', size=70)
    temperText = ft.Text('21', size=60,
                         color="black",
                         weight=ft.FontWeight.BOLD)

    weather_imogi = ft.Row([iconImage, temperText],
                           alignment=ft.MainAxisAlignment.CENTER)

    weather_text = ft.Text('Sunny',
                           weight=ft.FontWeight.BOLD, size=35, color="white")

    inputBox = ft.TextField(hint_text="search", border_width=5, bgcolor="black")
    weather_search = ft.Row([ft.IconButton(
        icon=ft.icons.REPLY,
        icon_size=30,
        on_click=get_weather),
        ft.Container(content=inputBox, width=150),
        ft.IconButton(icon=ft.icons.SEARCH, icon_size=30,
                      on_click=get_weather)],
        alignment=ft.MainAxisAlignment.CENTER
    )

    weatherInfo = ft.Column([weather_city,
                             weather_imogi,
                             weather_text,
                             weather_search],
                            alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
                            horizontal_alignment=ft.CrossAxisAlignment.CENTER)

    body = ft.Container(width=300,
                        image=ft.DecorationImage("https://picsum.photos/300/300?1", fit="fill"),
                        border_radius=ft.border_radius.all(30),
                        expand=1,
                        content=weatherInfo)
    page.add(body)


ft.app(main)

주요 라이브러리

  • flet: Python으로 GUI 앱을 작성할 수 있는 라이브러리입니다. 플러터(Flutter)와 유사한 구조를 통해 앱을 쉽고 빠르게 제작할 수 있습니다.
  • python_weather: 날씨 정보를 가져오기 위한 Python API 클라이언트입니다. 기본적으로 날씨 데이터를 검색할 수 있는 비동기 클라이언트를 제공합니다.

주요 함수들

  1. get_weather_icon(condition)
  • condition 변수에 따라 적절한 이모지 형태의 날씨 아이콘을 반환합니다.
  • 예를 들어 "Thunderstorm"이면 '🌩️'을 반환하고, "Sunny"면 '☀️'을 반환합니다.
  • 조건에 따라 다양한 날씨 상태를 시각적으로 표현할 수 있도록 돕는 유틸리티 함수입니다.
  1. to_celsius(fa)
  • fa에 입력된 화씨(°F) 온도를 섭씨(°C)로 변환하는 함수입니다.
  • 계산식은 (fa - 32) * 5.0 / 9.0이며, 소수점 첫째 자리에서 반올림한 값을 반환합니다.

메인 함수 (main(page: ft.Page))

이 함수는 전체 UI와 동작을 설정합니다.

  1. get_weather(e)
  • 날씨를 검색하는 비동기 함수입니다.
  • 사용자가 입력한 도시명(inputBox.value)을 사용해 날씨를 가져오고, 그 정보를 UI 요소들에 업데이트합니다.
  • python_weather.Client를 사용하여 날씨 데이터를 받아오며, unit=python_weather.IMPERIAL로 설정했기 때문에 화씨 단위를 사용하고 있습니다.
  • 데이터 업데이트가 완료되면 page.update()를 호출하여 UI가 새로 고쳐집니다.
  1. 페이지 설정
  • page.title: 페이지의 제목을 "Weather Layout"으로 설정합니다.
  • page.window.widthpage.window.height: 창의 너비와 높이를 각각 300과 350으로 설정합니다.
  • page.horizontal_alignment, page.vertical_alignment: 페이지의 수평 및 수직 정렬을 "center"로 설정하여 모든 요소가 중앙에 위치하도록 합니다.
  1. UI 요소 구성
  • cityName: 현재 도시명을 표시하는 ElevatedButton입니다. 기본값은 'seoul'입니다.
  • humidityPercent: 습도를 표시하는 텍스트 요소입니다. 기본값은 '💧 22%'입니다. 이 텍스트는 파란색("blueaccent")이며, 굵게(ft.FontWeight.BOLD) 설정되어 있습니다.
  • weather_city: cityNamehumidityPercent를 포함하는 Container입니다. Row 레이아웃을 사용하여 두 요소를 가로로 배치하며, alignment 속성으로 요소 간의 간격을 띄워 정렬합니다.
  • iconImage: 날씨 상태에 맞는 이모지를 표시하는 텍스트 요소로 기본값은 '☀️'입니다.
  • temperText: 온도를 표시하는 텍스트로 기본값은 '21'입니다.
  • weather_imogi: 날씨 이모지(iconImage)와 온도(temperText)를 가로로 배치한 Row입니다.
  • weather_text: 날씨 상태를 텍스트로 표현하는 요소로, 기본값은 'Sunny'입니다.
  • inputBox: 사용자가 검색할 도시명을 입력하는 TextField입니다. 배경색은 검정("black")이며, 검색을 위한 입력 필드입니다.
  • weather_search: 검색과 관련된 UI 요소로, 검색 아이콘 버튼과 inputBox를 가로로 배치한 Row입니다. on_click 이벤트로 get_weather 함수를 호출하여 검색 기능을 동작하도록 구성되어 있습니다.
  • weatherInfo: 날씨와 관련된 모든 정보를 담고 있는 Column입니다. 이 Column에는 weather_city, weather_imogi, weather_text, weather_search가 포함되어 있습니다. 전체적으로 수직으로 정렬되며, 가로 정렬은 중앙(ft.CrossAxisAlignment.CENTER)에 맞춰져 있습니다.
  1. body
  • 날씨 정보 전체를 감싸는 컨테이너로, 배경 이미지를 설정하여 앱의 비주얼을 개선합니다.
  • DecorationImage을 사용해 이미지(https://picsum.photos/300/300?1 - 랜덤 이미지)와 컨테이너를 꾸며줍니다.
  • border_radius로 컨테이너의 모서리를 둥글게 만들어 좀 더 부드러운 느낌을 줍니다.
  • weatherInfo는 이 body 컨테이너의 내용(content)으로 설정되어 있습니다.
  1. page.add(body)
  • 모든 UI 요소들이 포함된 body 컨테이너를 페이지에 추가합니다.
  1. ft.app(main)
  • flet 앱의 진입점으로, main 함수가 앱의 실행을 담당합니다.

개선된 날씨 앱

import flet as ft
import python_weather
from flet_toast import flet_toast


# Functions
def get_weather_icon(condition):
    weather_icons = {
        "Thunderstorm": '🌩️',
        "Drizzle": '🌧️',
        "Rainy": '☔',
        "Snowy": '☃️',
        "Windy": '🌀',
        "Sunny": '☀️',
        "Cloudy": '☁️',
        "Clear": '🌝'
    }
    return weather_icons.get(condition, '😁')


def to_celsius(fahrenheit):
    return round((int(fahrenheit) - 32) * 5.0 / 9.0, 1)


async def fetch_weather(location):
    async with python_weather.Client(unit=python_weather.IMPERIAL) as client:
        return await client.get(location)


def main(page: ft.Page):
    async def get_weather(e):
        if not input_box.value:
            weather_text.value = "Please enter a city name."
            page.update()
            return

        try:
            weather = await fetch_weather(input_box.value)
            temperature = to_celsius(weather.temperature)

            # Update UI elements
            temperature_text.value = f"{temperature} °C"
            humidity_text.value = f"💧 {weather.humidity}%"
            city_name.value = input_box.value.capitalize()
            icon_text.value = get_weather_icon(weather.description)
            weather_description.value = weather.description.capitalize()
            weather_text.value = ""
        except Exception as ex:
            flet_toast.warning(
                page=page,
                message='Could not retrieve weather information. Please check the city name.',
                position=flet_toast.Position.BOTTOM_RIGHT,
                duration=5
            )
        page.update()

    # Page settings
    page.title = "Weather App"
    page.window.width = 300
    page.window.height = 400
    page.horizontal_alignment = page.vertical_alignment = "center"

    # UI elements
    city_name = ft.Text('', 
                        weight=ft.FontWeight.BOLD, 
                        size=20)

    humidity_text = ft.Text('', 
                            color="blueaccent", 
                            size=20, 
                            weight=ft.FontWeight.BOLD)

    weather_header = ft.Container(
        content=ft.Row([
            city_name,
            humidity_text
        ], alignment=ft.MainAxisAlignment.SPACE_BETWEEN),
        margin=10
    )

    icon_text = ft.Text('☀️',
                        size=70)

    temperature_text = ft.Text('21',
                               size=60,
                               color="black",
                               weight=ft.FontWeight.BOLD)
    weather_icon_row = ft.Row([
        icon_text,
        temperature_text],
        alignment=ft.MainAxisAlignment.CENTER)

    weather_description = ft.Text('Sunny',
                                  weight=ft.FontWeight.BOLD,
                                  size=35,
                                  color="white")

    weather_text = ft.Text('',
                           color="red",
                           size=16)

    input_box = ft.TextField(hint_text="Enter city",
                             border_width=2,
                             bgcolor="white",
                             color="black",
                             on_submit=get_weather)

    search_button = ft.IconButton(icon=ft.icons.SEARCH,
                                  icon_size=30,
                                  on_click=get_weather)

    weather_search_row = ft.Row([
        search_button,
        ft.Container(content=input_box,
                     width=150)
    ], alignment=ft.MainAxisAlignment.CENTER)

    # Weather info layout
    weather_info = ft.Column([
        weather_header,
        weather_icon_row,
        weather_description,
        weather_search_row,
        weather_text
    ], 
        alignment=ft.MainAxisAlignment.SPACE_BETWEEN,
        horizontal_alignment=ft.CrossAxisAlignment.CENTER)

    # Background container
    body = ft.Container(
        width=300,
        image=ft.DecorationImage("https://picsum.photos/300/300?1", fit="fill"),
        border_radius=ft.border_radius.all(30),
        expand=1,
        content=weather_info
    )

    page.add(body)

ft.app(main)

아래는 코드 개선을 통해 더 효율적이고 가독성이 높은 날씨 어플리케이션을 만들어 보았습니다. 개선 사항에는 함수의 최적화, UI 요소의 재사용, 코드 중복 제거 등이 포함되어 있습니다.

개선된 사항 설명

  1. 함수 최적화
  • get_weather_icon(condition) 함수에서 조건문을 딕셔너리(weather_icons)로 변환하여 가독성을 높이고 유지보수를 쉽게 했습니다.
  1. 비동기 코드 개선
  • fetch_weather(location) 함수를 따로 만들어서 코드의 재사용성을 높였습니다.
  1. 오류 처리
  • 사용자가 잘못된 도시명을 입력하거나 인터넷 연결 문제로 인해 날씨 정보를 가져오지 못하는 경우를 대비해 예외 처리(try-except)를 추가했습니다.
  1. 코드 간소화 및 가독성 향상
  • 중복되거나 불필요한 변수를 제거하고 코드를 간결하게 정리했습니다.
  • 날씨 상태 업데이트 관련 코드를 그룹화하여 각 UI 업데이트가 한 번에 일어나도록 했습니다.
  1. 사용자 경험 향상
  • 도시명을 입력하지 않았을 경우 사용자에게 알림 메시지를 표시하도록 하여 더 직관적인 경험을 제공합니다.
  • 도시명은 capitalize() 메서드를 사용해 첫 글자만 대문자로 표기하여 가독성을 높였습니다.

이제 이 개선된 코드로 더 효율적이고 유지보수하기 쉬운 날씨 어플리케이션을 사용할 수 있을 것입니다. 추가적인 개선이나 기능 추가에 대해 궁금한 점이 있으면 말씀해 주세요!

728x90