밖에 비가 내리고있다? Python으로 Weather CLI 앱 구축

2023. 12. 22. 18:59python/basic

  • Python 표준 라이브러리 모듈 도구만 사용하여 기능적인 날씨 조회
  • argparse를 사용하여 Python CLI 앱 구축
  • API 비밀번호 사용하여 configparser 처리
  • Python 스크립트에서 API 호출 수행
  • ANSI 이스케이프 코드, 이모티콘, f-문자열 및 Python의 문자열 미니 언어를 사용하여 시각적으로 매력적인 CLI 출력 만들기

프로젝트 개요

코드 작성을 시작하기 전에 빌드하려는 프로그램의 사양에 대해 생각해 보는 것이 좋습니다. 먼저 펜과 종이를 꺼내 완벽한 날씨 앱이 어떤 모습일지 아이디어를 적어보세요. 아이디어를 메모한 후에는 아래 제목을 클릭하여 이 튜토리얼을 진행할 때 고려할 사양을 읽을 수 있습니다.m

이 튜토리얼에서 구축할 날씨 앱은 다음과 같습니다.

  • 필수 입력으로 도시 이름을 사용하세요.
  • 원하는 경우 섭씨 대신 화씨로 출력을 표시하려면 선택적 플래그를 사용하세요.
  • 온라인 날씨 API를 호출하여 날씨 데이터를 가져옵니다.
  • 도시 이름, 현재 기상 조건, 현재 온도를 표시합니다.
  • 색상, 간격, 이모티콘을 사용하여 시각적으로 출력 형식 지정하세요

빠른 메모를 통해 빌드할 항목에 대한 대략적인 계획을 세우고 앱이 수행할 수 있는 범위를 정의했습니다.

이 튜토리얼에서는 사용자 친화적인 CLI 앱을 만드는 데 도움이 되는 Python의 내장 argparse 모듈을 사용하여 작업하게 됩니다.

전제조건

  • argparse을 사용하여 명령줄 인터페이스 구축
  • API를 사용하여 인터넷에서 공개 데이터 읽기
  • Python 코드에 API 통합
  • JSON 데이터 작업
  • 모듈 및 라이브러리 가져오기
  • 자신만의 기능 정의
  • Python 예외로 인한 오류 처리
  • F-문자열을 사용하여 텍스트 서식 지정
  • 조건식 사용

1단계: 적합한 Weather API에 액세스하기

이 단계에서는 프로젝트에 적합한 날씨 API를 선택하고 개인 API 키에 액세스하며 API에서 정보를 요청하는 방법을 배우게 됩니다.

OpenWeather API 키 받기

개인 API 키를 받으려면 해당 플랫폼에 가입해야 합니다. 가입이 성공적으로 완료되면 API 키가 포함된 확인 이메일을 받게 됩니다. 이제 OpenWeather 웹 인터페이스에 로그인하고 계정 페이지의 API 키 탭으로 이동하여 API 키를 볼 수도 있습니다.

참고: 가입 후 API 키가 활성화되는 데 몇 시간 정도 걸릴 수 있습니다.

API 요청 및 데이터 검사

OpenWeather 웹사이트의 API 문서에는 날씨 데이터를 수신하기 위해 엔드포인트에 수행할 수 있는 요청 예제가 표시되어 있습니다.

# 텍스트

api.openweathermap.org/data/2.5/weather?q={city name}&appid={API key}

중괄호({})로 표시된 두 변수를 입력 내용으로 바꿔야 합니다.

  1. {city name}을 vienna 또는 다른 도시 이름으로 바꾸세요.
  2. {API key}을 이전에 복사한 API 키로 바꾸세요.

이 두 값을 바꾼 후 URL을 브라우저에 붙여넣고 API 응답을 볼 수 있습니다.

브라우저에 위 스크린샷에 표시된 것과 유사한 텍스트가 표시됩니다. 이 텍스트는 귀하의 요청에 대한 OpenWeather의 JSON API 응답입니다.

# JSON API 응답
In [ ]:
# JSON

{
  "coord": {
    "lon": 16.3721,
    "lat": 48.2085
  },
  "weather": [
    {
      "id": 801,
      "main": "Clouds",
      "description": "few clouds",
      "icon": "02d"
    }
  ],
  "base": "stations",
  "main": {
    "temp": 287.72,
    "feels_like": 286.98,
    "temp_min": 285.99,
    "temp_max": 290.48,
    "pressure": 1028,
    "humidity": 67
  },
  "visibility": 10000,
  "wind": {
    "speed": 1.34,
    "deg": 100,
    "gust": 1.34
  },
  "clouds": {
    "all": 20
  },
  "dt": 1635333309,
  "sys": {
    "type": 2,
    "id": 2037452,
    "country": "AT",
    "sunrise": 1635312722,
    "sunset": 1635349469
  },
  "timezone": 7200,
  "id": 2761369,
  "name": "Vienna",
  "cod": 200
}

앞서 최소한 세 가지 항목을 표시하기로 결정했습니다.

  1. 도시 이름
  2. 기상 조건
  3. 온도

제공된 JSON 응답에서 이 정보를 검색하면 해당 정보가 모두 포함되어 있음을 알 수 있습니다.

  1. 42행은 도시 이름 "Vienna"을 키 "name" 아래에 표시합니다.
  2. 10행 키 "weather" 및 하위 키 "description" 아래에 자세한 날씨 정보가 들어 있습니다.
  3. 16행에서는 키 "main" 및 하위 키 "temp" 아래의 온도 정보에 액세스할 수 있습니다.

API 문서를 다시 확인하면 기본 온도 단위가 켈빈임을 알 수 있습니다. 하지만 날씨 앱에서는 온도 눈금을 섭씨로 기본 설정하는 것이 좋습니다.

# 텍스트

api.openweathermap.org/data/2.5/weather?q=vienna&units=metric&appid={API key}

검색어 매개변수&units=metric를 URL에 추가하면 섭씨로 표시된 온도 결과를 받게 됩니다.

In [ ]:
# JSON

"main": {
  "temp": 14.57,
  "feels_like": 13.83,
  "temp_min": 12.84,
  "temp_max": 17.33,
  "pressure": 1028,
  "humidity": 67
},

2단계: 코드의 비밀 처리

프로젝트 폴더 구조 만들기

In [ ]:
!mkdir weather-app && cd weather-app
!touch .gitignore secrets.ini weather.py

셸에서 이 명령을 실행하면 weather-app/라는 폴더가 생성됩니다. 여기에는 세 개의 파일이 포함됩니다.

  1. .gitignore실수로 API 키를 버전 관리에 적용하는 것을 방지할 수 있습니다.
  2. secrets.ini귀하의 API 키를 보유하게 됩니다.
  3. weather.pyPython 날씨 앱의 기본 코드 로직이 포함됩니다.

weather-app/ ├── .gitignore ├── secrets.ini └── weather.py

API 키를 보호하세요

secrets.ini을 사용하여 이 프로젝트에 필요한 API 키를 저장합니다. .gitignore 파일을 열고 무시된 파일에 secrets.ini을 추가하세요.

# .gitignore

secrets.ini

Python 코드에서 API 키에 액세스

secrets.ini 파일을 열고 INI 파일 형식에 따라 정보를 추가하세요.

# config file

; secrets.ini

[openweather]
api_key=<YOUR-OPENWEATHER-API-KEY>

In [ ]:
# weather.py

from configparser import ConfigParser

def _get_api_key():
    """Fetch the API key from your configuration file.

    Expects a configuration file named "secrets.ini" with structure:

        [openweather]
        api_key=<YOUR-OPENWEATHER-API-KEY>
    """
    config = ConfigParser()
    config.read("secrets.ini")
    return config["openweather"]["api_key"]
  • 3번째 줄은 Python의 모듈에서 ConfigParser를 가져옵니다.
  • 5행은 get_api_key()를 정의하며 밑줄 () 문자입니다. 이 명명 규칙은 함수가 비공개로 간주되어야 함을 나타냅니다.
  • 6~12행 함수에 대한 docstring을 구성합니다.
  • 13번째 줄은 ConfigParser 이름을 지정한 개체를 config 인스턴스화합니다.
  • 14행은 .read()를 사용하여 secrets.ini에 저장한 정보를 Python 스크립트로 로드합니다.
  • 15행은 대괄호를 사용하여 사전 값에 액세스하여 API 키 값을 반환합니다.

3단계: Python 날씨 앱용 CLI 생성

In [ ]:
# weather.py

import argparse
from configparser import ConfigParser

def read_user_cli_args():
    """Handles the CLI user interactions.

    Returns:
        argparse.Namespace: Populated namespace object
    """
    parser = argparse.ArgumentParser(
        description="gets weather and temperature information for a city"
    )
    return parser.parse_args()

# ...

if __name__ == "__main__":
    read_user_cli_args()
  • 12~14행 argparse.ArgumentParser의 인스턴스를 생성하고, 여기에 13행의 파서에 대한 선택적 설명을 전달합니다.
  • 15행은 .parse_args()에 대한 호출 결과를 반환하며, 이는 최종적으로 사용자 입력 값이 됩니다.
  • 19행은 weather.py 스크립트를 실행할 때 실행해야 하는 코드를 정의하기 위하여 Python의 "main" 네임스페이스를 체크한 뒤 조건 블럭을 엽니다.
  • 20행은 호출read_user_cli_args()은 추가로 작성한 CLI 구문 분석 코드 논리를 효과적으로 실행합니다.

입력 인수 구문 분석

In [ ]:
# weather.py

import argparse
from configparser import ConfigParser

def read_user_cli_args():
    """Handles the CLI user interactions.

    Returns:
        argparse.Namespace: Populated namespace object
    """
    parser = argparse.ArgumentParser(
        description="gets weather and temperature information for a city"
    )
    parser.add_argument(
        "city", nargs="+", type=str, help="enter the city name"
    )
    parser.add_argument(
        "-i",
        "--imperial",
        action="store_true",
        help="display the temperature in imperial units",
    )
    return parser.parse_args()

# ...

if __name__ == "__main__":
    read_user_cli_args()
  • 15~17행은 공백으로 구분된 하나 이상의 입력을 받는 "city" 인수를 정의합니다.
  • 18~23행 선택적 부울 인수를 정의합니다 imperial.

그러나 스크립트를 실행하고 도시 이름을 입력으로 전달하면 여전히 콘솔에 다시 표시되는 출력을 볼 수 없습니다. weather.py로 돌아가서 파일 하단에 있는 조건부 코드 블록의 코드를 편집하세요.

In [ ]:
# weather.py

# ...

if __name__ == "__main__":
    user_args = read_user_cli_args()
    print(user_args.city, user_args.imperial)

4단계: 날씨 정보 얻기

URL 구축

In [ ]:
# weather.py

import argparse
from configparser import ConfigParser

BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"

# ...

이제 얻을 수 있는 입력을 바탕으로 완전한 URL을 생성할 차례입니다.

In [ ]:
# weather.py

import argparse
from configparser import ConfigParser
from urllib import parse

BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"

def read_user_cli_args():
    # ...

def build_weather_query(city_input, imperial=False):
    """Builds the URL for an API request to OpenWeather's weather API.

    Args:
        city_input (List[str]): Name of a city as collected by argparse
        imperial (bool): Whether or not to use imperial units for temperature

    Returns:
        str: URL formatted for a call to OpenWeather's city name endpoint
    """
    api_key = _get_api_key()
    city_name = " ".join(city_input)
    url_encoded_city_name = parse.quote_plus(city_name)
    units = "imperial" if imperial else "metric"
    url = (
        f"{BASE_WEATHER_API_URL}?q={url_encoded_city_name}"
        f"&units={units}&appid={api_key}"
    )
    return url

# ...

5번째 줄에서 새 가져오기 문을 추가하는 것부터 시작했습니다. 24번째 줄에서 urllib.parse 모듈의 함수를 사용하여 API가 안전하게 작업할 수 있도록 사용자 입력을 삭제합니다. 소비하세요.

12행에서는 CLI를 통해 수집한 사용자 입력에 해당하는 두 개의 입력을 받는 build_weather_query()을 정의했습니다.

  1. city_input은 user_args.city에서 수집한 문자열 목록입니다.
  2. imperial은 온도를 섭씨로 표시할지 화씨로 표시할지 결정하는 부울 값입니다. 이 인수의 기본값은 False 입니다.

여러분이 작성한 함수는 설명적인 독스트링이 포함된 몇 줄의 코드로 구성됩니다.

  • 라인 22는 _get_api_key()를 호출하여 구성 파일에서 OpenWeather API 키를 가져와서 api_key으로 저장합니다.
  • 23행은 str.join()를 사용하여 도시 이름을 구성하는 단어를 공백 문자(" ")와 연결합니다.-
  • 라인 24는 city_name를 parse.quote_plus()에 전달하며, 이는 유효한 HTTP 요청을 할 수 있도록 문자열을 인코딩합니다. API에. UTF-8 인코딩을 통해 특정 문자를 변환하는 것 외에도 이 함수는 공백 문자를 형식인 더하기 기호()로 변환합니다. URL encoding이 API를 적절하게 호출하는 데 필요합니다.
  • 25번째 줄은 imperial 매개변수가 True 혹은 False 인지 여부에 따라 units에 "imperial" 또는 "metric" 인지를 지정하는 conditional expression를 사용합니다.
  • 26~29행은 성공적인 API 호출을 만드는 데 사용할 수 있는 전체 URL을 작성합니다. f-문자열을 사용하여 BASE_WEATHER_API_URL 및 이전 줄에서 할당한 변수에서 URL을 구성합니다.
  • 라인 30은 마침내 완전한 형식의 URL을 반환합니다.
In [ ]:
# weather.py

# ...

if __name__ == "__main__":
    user_args = read_user_cli_args()
    query_url = build_weather_query(user_args.city, user_args.imperial)
    print(query_url)

Python으로 HTTP 요청 만들기

In [ ]:
# weather.py

import argparse
import json
from configparser import ConfigParser
from urllib import parse, request

# ...

urllib.request을 사용하여 요청을 하고, json를 사용하여 API 응답 데이터를 Python 사전으로 변환합니다.

In [ ]:
# weather.py

# ...

def get_weather_data(query_url):
    """Makes an API request to a URL and returns the data as a Python object.

    Args:
        query_url (str): URL formatted for OpenWeather's city name endpoint

    Returns:
        dict: Weather information for a specific city
    """
    response = request.urlopen(query_url)
    data = response.read()
    return json.loads(data)


if __name__ == "__main__":
    user_args = read_user_cli_args()
    query_url = build_weather_query(user_args.city, user_args.imperial)
    weather_data = get_weather_data(query_url)
    print(weather_data)
  • 5행은 날씨 API 엔드포인트에 요청하는 데 사용할 함수인 get_weather_data()를 정의합니다.
  • 6~13행 함수에 대한 독스트링을 구성합니다.
  • 14행은 urllib.request.urlopen()를 사용하여 query_url 매개변수에 대한 HTTP GET 요청을 만들고 결과를 response로 저장합니다.
  • 라인 15는 응답에서 데이터를 추출합니다.
  • 라인 16은 인수로 사용하여 json.loads()에 대한 호출을 반환합니다. 이 함수는 dataquery_url에서 가져온 JSON 정보가 포함된 Python 객체를 반환합니다.
  • 라인 22는 query_url build_weather_query() 함께 생성한 query_url에 전달하는 get_weather_data()을 호출하고 그런 다음 사전을 weather_data에 저장합니다.
  • 라인 23 마침내 날씨 데이터를 콘솔에 인쇄합니다.

코드에서 예외 처리

In [ ]:
# weather.py

import argparse
import json
import sys
from configparser import ConfigParser
from urllib import error, parse, request

# ...

def get_weather_data(query_url):

    # ...

    try:
        response = request.urlopen(query_url)
    except error.HTTPError:
        sys.exit("Can't find weather data for this city.")

    data = response.read()
    return json.loads(data)

# ...
  • 5행은 18행에서 수행하는 역추적 없이 프로그램을 정상적으로 종료할 수 있게 해주는 내장 sys 모듈을 가져옵니다.
  • 7행은 urllib로 부터 import에 error를 추가합니다.
  • 라인 15는 try … except 블록을 시작하며 다음을 수행하는 코드 라인을 이동했습니다. HTTP 요청을 16행의 try 블록에 넣습니다.
  • 17행은 16행의 HTTP 요청 중에 발생하는 모든 error.HTTPError을 포착합니다. except 블록을 오픈합니다.
  • 라인 18은 HTTP 오류가 발생할 경우 애플리케이션을 정상적으로 종료하도록 sys.exit()를 호출합니다. 또한 사용자 입력에 대한 날씨 데이터가 없을 때 CLI가 표시할 설명 메시지를 전달합니다.

다양한 HTTP 오류 코드에 대해 메시지를 더욱 구체적으로 작성하여 예외 처리를 개선할 수 있습니다.

In [ ]:
# weather.py

# ...

def get_weather_data(query_url):

    # ...

    try:
        response = request.urlopen(query_url)
    except error.HTTPError as http_error:
        if http_error.code == 401:  # 401 - Unauthorized
            sys.exit("Access denied. Check your API key.")
        elif http_error.code == 404:  # 404 - Not Found
            sys.exit("Can't find weather data for this city.")
        else:
            sys.exit(f"Something went wrong... ({http_error.code})")

    data = response.read()
    return json.loads(data)

# ...

좋은 측정을 위해 다른 try … except 블록을 추가하여 API가 보낼 수 있는 잠재적으로 잘못된 형식의 JSON을 처리합니다.

In [ ]:
# weather.py

# Removed: from pprint import pp

# ...
if __name__ == "__main__":

    # ...

    print(
      f"{weather_data['name']}: "
      f"{weather_data['weather'][0]['description']} "
      f"({weather_data['main']['temp']})"
      )

디스플레이 기능 구축

In [ ]:
# weather.py

# ...

def display_weather_info(weather_data, imperial=False):
    """Prints formatted weather information about a city.

    Args:
        weather_data (dict): API response from OpenWeather by city name
        imperial (bool): Whether or not to use imperial units for temperature

    More information at https://openweathermap.org/current#name
    """
    city = weather_data["name"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    print(f"{city}", end="")
    print(f"\t{weather_description.capitalize()}", end=" ")
    print(f"({temperature}°{'F' if imperial else 'C'})")


if __name__ == "__main__":

    # ...

    display_weather_info(weather_data, user_args.imperial)

출력에 문자열 패딩 추가

In [ ]:
print(f"{city:^20}", end="")
print(
    f"\t{weather_description.capitalize():^20}",
    end=" ",
)
print(f"({temperature}°{'F' if imperial else 'C'})")
In [ ]:
# weather.py

import argparse
import json
import sys
from configparser import ConfigParser

from urllib import error, parse, request

BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"
PADDING = 20

# ...

def display_weather_info(weather_data, imperial=False):

    # ...

    print(f"{city:^{PADDING}}", end="")
    print(
        f"\t{weather_description.capitalize():^{PADDING}}",
        end=" ",
    )
    print(f"({temperature}°{'F' if imperial else 'C'})")
In [ ]:
# weather.py 전체 코드

# weather.py

import argparse
import json
import sys
from configparser import ConfigParser
from urllib import error, parse, request

BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"
PADDING = 20

def read_user_cli_args():
    """Handles the CLI user interactions.

    Returns:
        argparse.Namespace: Populated namespace object
    """
    parser = argparse.ArgumentParser(
        description="gets weather and temperature information for a city"
    )
    parser.add_argument(
        "city", nargs="+", type=str, help="enter the city name"
    )
    parser.add_argument(
        "-i",
        "--imperial",
        action="store_true",
        help="display the temperature in imperial units",
    )
    return parser.parse_args()


def build_weather_query(city_input, imperial=False):
    """Builds the URL for an API request to OpenWeather's weather API.

    Args:
        city_input (List[str]): Name of a city as collected by argparse
        imperial (bool): Whether or not to use imperial units for temperature

    Returns:
        str: URL formatted for a call to OpenWeather's city name endpoint
    """
    api_key = _get_api_key()
    city_name = " ".join(city_input)
    url_encoded_city_name = parse.quote_plus(city_name)
    units = "imperial" if imperial else "metric"
    url = (
        f"{BASE_WEATHER_API_URL}?q={url_encoded_city_name}"
        f"&units={units}&appid={api_key}"
    )
    return url


def _get_api_key():
    """Fetch the API key from your configuration file.

    Expects a configuration file named "secrets.ini" with structure:

        [openweather]
        api_key=<YOUR-OPENWEATHER-API-KEY>
    """
    config = ConfigParser()
    config.read("secrets.ini")
    return config["openweather"]["api_key"]


def get_weather_data(query_url):
    """Makes an API request to a URL and returns the data as a Python object.

    Args:
        query_url (str): URL formatted for OpenWeather's city name endpoint

    Returns:
        dict: Weather information for a specific city
    """
    try:
        response = request.urlopen(query_url)
    except error.HTTPError as http_error:
        if http_error.code == 401:  # 401 - Unauthorized
            sys.exit("Access denied. Check your API key.")
        elif http_error.code == 404:  # 404 - Not Found
            sys.exit("Can't find weather data for this city.")
        else:
            sys.exit(f"Something went wrong... ({http_error.code})")

    data = response.read()

    try:
        return json.loads(data)
    except json.JSONDecodeError:
        sys.exit("Couldn't read the server response.")


def display_weather_info(weather_data, imperial=False):
    """Prints formatted weather information about a city.

    Args:
        weather_data (dict): API response from OpenWeather by city name
        imperial (bool): Whether or not to use imperial units for temperature

    More information at https://openweathermap.org/current#name
    """
    city = weather_data["name"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    print(f"{city:^{PADDING}}", end="")
    print(
        f"\t{weather_description.capitalize():^{PADDING}}",
        end=" ",
    )
    print(f"({temperature}°{'F' if imperial else 'C'})")


if __name__ == "__main__":
    user_args = read_user_cli_args()
    query_url = build_weather_query(user_args.city, user_args.imperial)
    weather_data = get_weather_data(query_url)
    display_weather_info(weather_data, user_args.imperial)

6단계: 날씨 앱의 출력 스타일 지정

터미널 출력 색상 변경

In [ ]:
# weather.py

# ...

PADDING = 20
REVERSE = "\033[;7m"
RESET = "\033[0m"

# ...

def display_weather_info(weather_data, imperial=False):

    # ...

    city = weather_data["name"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    print(f"{REVERSE}{city:^{PADDING}}{RESET}", end="")
    print(
        f"\t{weather_description.capitalize():^{PADDING}}",
        end=" ",
    )
    print(f"({temperature}°{'F' if imperial else 'C'})")

# ...
In [ ]:
# style.py

PADDING = 20

REVERSE = "\033[;7m"
RESET = "\033[0m"

def change_color(color):
    print(color, end="")
In [ ]:
# weather.py

import argparse
import json
import sys
from configparser import ConfigParser
from urllib import error, request

import style

BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"
# Remove: PADDING = 20
# Remove: REVERSE = "\033[;7m"
# Remove: RESET = "\033[0m"

# ...

def display_weather_info(weather_data, imperial=False):

    # ...

    city = weather_data["name"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    style.change_color(style.REVERSE)
    print(f"{city:^{style.PADDING}}", end="")
    style.change_color(style.RESET)
    print(
        f"\t{weather_description.capitalize():^{style.PADDING}}",
        end=" ",
    )
    print(f"({temperature}°{'F' if imperial else 'C'})")

# ...

이 리팩토링을 통해 새 style 모듈을 가져오고 이전에 weather.py에서 정의한 스타일 관련 상수를 제거했습니다. 그런 다음 새 모듈에서 정의한 설명 을 사용하기 위해 print() 호출을 리팩터링했습니다.

In [ ]:
# style.py

PADDING = 20

RED = "\033[1;31m"
BLUE = "\033[1;34m"
CYAN = "\033[1;36m"
GREEN = "\033[0;32m"
YELLOW = "\033[33m"
WHITE = "\033[37m"

REVERSE = "\033[;7m"
RESET = "\033[0m"

def change_color(color):
    print(color, end="")

이를 사용하면 눈에 띄는 빨간색으로 날씨 설명의 형식을 지정한 다음 온도를 표시하기 전에 기본값으로 재설정할 수 있습니다.

In [ ]:
# weather.py

# ...

def display_weather_info(weather_data, imperial=False):

    # ...

    city = weather_data["name"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    style.change_color(style.REVERSE)
    print(f"{city:^{style.PADDING}}", end="")
    style.change_color(style.RESET)

    style.change_color(style.RED)
    print(
        f"\t{weather_description.capitalize():^{style.PADDING}}",
        end=" ",
    )
    style.change_color(style.RESET)

    print(f"({temperature}°{'F' if imperial else 'C'})")

# ...

날씨 유형을 다양한 색상으로 포맷

In [ ]:
# weather.py

import argparse
import json
import sys
from configparser import ConfigParser
from urllib import error, parse, request

import style

BASE_WEATHER_API_URL = "http://api.openweathermap.org/data/2.5/weather"

# Weather Condition Codes
# https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2
THUNDERSTORM = range(200, 300)
DRIZZLE = range(300, 400)
RAIN = range(500, 600)
SNOW = range(600, 700)
ATMOSPHERE = range(700, 800)
CLEAR = range(800, 801)
CLOUDY = range(801, 900)

# ...
In [ ]:
# weather.py

# ...

def display_weather_info(weather_data, imperial=False):

    # ...

    city = weather_data["name"]
    weather_id = weather_data["weather"][0]["id"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    style.change_color(style.REVERSE)
    print(f"{city:^{style.PADDING}}", end="")
    style.change_color(style.RESET)

    if weather_id in THUNDERSTORM:
        style.change_color(style.RED)
    elif weather_id in DRIZZLE:
        style.change_color(style.CYAN)
    elif weather_id in RAIN:
        style.change_color(style.BLUE)
    elif weather_id in SNOW:
        style.change_color(style.WHITE)
    elif weather_id in ATMOSPHERE:
        style.change_color(style.BLUE)
    elif weather_id in CLEAR:
        style.change_color(style.YELLOW)
    elif weather_id in CLOUDY:
        style.change_color(style.WHITE)
    else:  # In case the API adds new weather codes
        style.change_color(style.RESET)
    print(
        f"\t{weather_description.capitalize():^{style.PADDING}}",
        end=" ",
    )
    style.change_color(style.RESET)

    print(f"({temperature}°{'F' if imperial else 'C'})")

# ...

코드를 리팩터링하고 이모티콘을 추가하세요

In [ ]:
# weather.py

# ...

def display_weather_info(weather_data, imperial=False):

    # ...

    city = weather_data["name"]
    weather_id = weather_data["weather"][0]["id"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    style.change_color(style.REVERSE)
    print(f"{city:^{style.PADDING}}", end="")
    style.change_color(style.RESET)

    color = _select_weather_display_params(weather_id)

    style.change_color(color)
    print(
        f"\t{weather_description.capitalize():^{style.PADDING}}",
        end=" ",
    )
    style.change_color(style.RESET)

    print(f"({temperature}°{'F' if imperial else 'C'})")

def _select_weather_display_params(weather_id):
    if weather_id in THUNDERSTORM:
        color = style.RED
    elif weather_id in DRIZZLE:
        color = style.CYAN
    elif weather_id in RAIN:
        color = style.BLUE
    elif weather_id in SNOW:
        color = style.WHITE
    elif weather_id in ATMOSPHERE:
        color = style.BLUE
    elif weather_id in CLEAR:
        color = style.YELLOW
    elif weather_id in CLOUDY:
        color = style.WHITE
    else:  # In case the API adds new weather codes
        color = style.RESET
    return color

# ...

이번 변경으로 조건문을 _select_weather_display_params()로 이동하고 display_weather_info()에 대체 기능으로 해당 함수에 대한 호출을 추가했습니다. 이렇게 하면 정보를 더욱 집중적이고 세부적으로 유지할 수 있으며, 이번 변경으로 스타일 관련 표시 매개변수를 추가할 수 있는 위치도 명확해졌습니다.
이모티콘을 추가하여 이러한 포함된 방식으로 출력 스타일을 개선하는 연습을 하게 됩니다.

In [ ]:
# weather.py

# ...

def display_weather_info(weather_data, imperial=False):

    # ...

    city = weather_data["name"]
    weather_id = weather_data["weather"][0]["id"]
    weather_description = weather_data["weather"][0]["description"]
    temperature = weather_data["main"]["temp"]

    style.change_color(style.REVERSE)
    print(f"{city:^{style.PADDING}}", end="")
    style.change_color(style.RESET)

    weather_symbol, color = _select_weather_display_params(weather_id)

    style.change_color(color)
    print(f"\t{weather_symbol}", end=" ")
    print(
        f"{weather_description.capitalize():^{style.PADDING}}",
        end=" ",
    )
    style.change_color(style.RESET)

    print(f"({temperature}°{'F' if imperial else 'C'})")

def _select_weather_display_params(weather_id):
    if weather_id in THUNDERSTORM:
        display_params = ("💥", style.RED)
    elif weather_id in DRIZZLE:
        display_params = ("💧", style.CYAN)
    elif weather_id in RAIN:
        display_params = ("💦", style.BLUE)
    elif weather_id in SNOW:
        display_params = ("⛄️", style.WHITE)
    elif weather_id in ATMOSPHERE:
        display_params = ("🌀", style.BLUE)
    elif weather_id in CLEAR:
        display_params = ("🔆", style.YELLOW)
    elif weather_id in CLOUDY:
        display_params = ("💨", style.WHITE)
    else:  # In case the API adds new weather codes
        display_params = ("🌈", style.RESET)
    return display_params

# ...

이번 업데이트에서는 각 날씨 ID에 이모티콘을 추가하고 두 개의 표시 매개변수를 튜플로 요약했습니다.

소스 코드 받기: 날씨 앱을 구축하는 데 사용할 소스 코드를 얻으려면 https://realpython.com/bonus/build-a-python-weather-app-cli-project-code/를 클릭하세요.