Python & AI Tutorials Logo
Python 프로그래밍

39. 필수 표준 라이브러리 모듈

Python의 표준 라이브러리(standard library) 는 Python에 기본으로 포함된 모듈들의 모음입니다. 별도로 설치하지 않아도 바로 사용할 수 있습니다. 이 모듈들은 난수 생성, 날짜와 시간 처리, 다른 프로그램과의 데이터 교환, 기본 리스트와 딕셔너리를 넘어서는 특수 데이터 구조처럼 흔한 프로그래밍 작업을 위한 강력한 도구를 제공합니다.

이 장에서는 실제 Python 프로그래밍에서 자주 사용하게 될, 필수 표준 라이브러리 모듈 다섯 가지를 살펴보겠습니다.

39.1) random으로 난수 생성하기

random 모듈은 난수를 생성하고 무작위 선택을 수행하는 함수들을 제공합니다. 이는 시뮬레이션, 게임, 테스트, 데이터 샘플링, 그리고 예측 불가능한 동작이 필요한 모든 상황에 유용합니다.

39.1.1) randint()로 무작위 정수 생성하기

randint() 함수는 두 값 사이의 무작위 정수를 생성하며, 양 끝 값을 모두 포함(inclusive) 합니다:

python
import random
 
# 6면체 주사위 굴리기 시뮬레이션
die_roll = random.randint(1, 6)
print(f"You rolled: {die_roll}")  # Output: You rolled: 4 (varies each run)
 
# 18에서 65 사이의 무작위 나이 생성
age = random.randint(18, 65)
print(f"Random age: {age}")  # Output: Random age: 42 (varies)

시작 값과 끝 값이 모두 가능한 결과에 포함된다는 점에 주목하세요. randint(1, 6)은 1, 2, 3, 4, 5, 6을 반환할 수 있으며, 여섯 값 모두 가능합니다.

다음은 여러 번의 주사위 굴림을 시뮬레이션하는 실용적인 예시입니다:

python
import random
 
# 두 개의 주사위를 굴려 합을 계산하는 시뮬레이션
die1 = random.randint(1, 6)
die2 = random.randint(1, 6)
total = die1 + die2
 
print(f"Die 1: {die1}")  # Output: Die 1: 3 (varies)
print(f"Die 2: {die2}")  # Output: Die 2: 5 (varies)
print(f"Total: {total}")  # Output: Total: 8 (varies)
 
if total == 7:
    print("Lucky seven!")
elif total == 2 or total == 12:
    print("Snake eyes or boxcars!")

왜 양 끝을 모두 포함하나요: 이렇게 하면 randint()가 흔한 사용 사례에서 직관적으로 동작합니다. (주사위처럼) 1부터 6까지의 숫자가 필요하면 randint(1, 6)이라고 쓰면 되고, 1과 6 모두 가능한 결과가 됩니다.

39.1.2) 무작위 부동소수점 숫자 생성하기

무작위 소수(decimal) 숫자가 필요하다면 random()(0.0과 1.0 사이의 float 반환) 또는 uniform()(지정한 두 값 사이의 float 반환)을 사용합니다:

python
import random
 
# 0.0과 1.0 사이의 무작위 float 생성 (0.0 포함, 1.0 제외)
probability = random.random()
print(f"Random probability: {probability:.4f}")  # Output: Random probability: 0.7284 (varies)
 
# 15.0도에서 30.0도 사이의 무작위 온도 생성
temperature = random.uniform(15.0, 30.0)
print(f"Temperature: {temperature:.2f}°C")  # Output: Temperature: 23.47°C (varies)
 
# $10.00에서 $99.99 사이의 무작위 가격 생성
price = random.uniform(10.0, 99.99)
print(f"Price: ${price:.2f}")  # Output: Price: $45.67 (varies)

random() 함수는 확률 값이나 퍼센트가 필요할 때 유용합니다. uniform() 함수는 특정 범위에서 무작위 소수가 필요할 때 더 적합합니다.

39.1.3) choice()로 무작위 선택하기

choice() 함수는 시퀀스(리스트, 튜플, 문자열)에서 요소 하나를 무작위로 선택합니다:

python
import random
 
# 무작위로 색 선택
colors = ["red", "blue", "green", "yellow", "purple"]
selected_color = random.choice(colors)
print(f"Selected color: {selected_color}")  # Output: Selected color: green (varies)
 
# 참가자 중 무작위로 우승자 선택
participants = ["Alice", "Bob", "Charlie", "Diana"]
winner = random.choice(participants)
print(f"The winner is: {winner}")  # Output: The winner is: Bob (varies)
 
# 문자열에서 무작위로 문자 선택
vowels = "aeiou"
random_vowel = random.choice(vowels)
print(f"Random vowel: {random_vowel}")  # Output: Random vowel: i (varies)

이는 특히 게임, 무작위 샘플링, 또는 무작위 테스트 데이터를 선택할 때 유용합니다. 시퀀스의 각 요소는 선택될 확률이 모두 같습니다.

다음은 간단한 퀴즈 게임을 시뮬레이션하는 좀 더 복잡한 예시입니다:

python
import random
 
# 퀴즈 질문과 정답
questions = [
    ("What is 2 + 2?", "4"),
    ("What is the capital of France?", "Paris"),
    ("What color is the sky?", "blue")
]
 
# 무작위로 질문 선택
question, correct_answer = random.choice(questions)
print(f"Question: {question}")
 
user_answer = input("Your answer: ")
if user_answer.lower() == correct_answer.lower():
    print("Correct!")
else:
    print(f"Wrong! The answer was: {correct_answer}")

39.1.4) sample()로 여러 개의 무작위 항목 선택하기

시퀀스에서 중복 없이 여러 개의 항목을 선택해야 한다면 sample()을 사용합니다. 이는 덱에서 카드를 교체 없이 뽑는 것과 같습니다. 한 번 선택된 항목은 다시 선택되지 않습니다:

python
import random
 
# 조별 과제를 위한 학생 3명 무작위 선택
students = ["Alice", "Bob", "Charlie", "Diana", "Eve", "Frank"]
group = random.sample(students, 3)
print(f"Group members: {group}")  # Output: Group members: ['Diana', 'Alice', 'Frank'] (varies)
 
# 1부터 50까지 로또 번호 5개 뽑기 (중복 없음)
lottery_numbers = random.sample(range(1, 51), 5)
lottery_numbers.sort()  # 표시를 위해 정렬
print(f"Lottery numbers: {lottery_numbers}")  # Output: Lottery numbers: [7, 15, 23, 38, 49] (varies)

sample()의 두 번째 인수는 선택할 항목 개수를 지정합니다. 이 숫자는 시퀀스 길이보다 작거나 같아야 합니다. 사용할 수 있는 항목보다 더 많이 선택할 수는 없습니다.

39.1.5) shuffle()로 시퀀스 섞기

shuffle() 함수는 리스트의 요소 순서를 제자리에서(in place) 무작위로 재배열합니다(원본 리스트를 수정합니다):

python
import random
 
# 카드 덱 섞기
cards = ["A♠", "K♠", "Q♠", "J♠", "10♠", "9♠", "8♠", "7♠"]
print(f"Original: {cards}")
random.shuffle(cards)
print(f"Shuffled: {cards}")  # Output: Shuffled: ['Q♠', '7♠', 'A♠', '10♠', '9♠', 'J♠', 'K♠', '8♠'] (varies)
 
# 퀴즈 질문을 무작위 순서로 섞기
questions = ["Question 1", "Question 2", "Question 3", "Question 4"]
random.shuffle(questions)
print(f"Randomized order: {questions}")  # Output: Randomized order: ['Question 3', 'Question 1', 'Question 4', 'Question 2'] (varies)

random 모듈 함수

randint: 무작위 정수

random/uniform: 무작위 실수

choice: 항목 1개 선택

sample: 중복 없이 여러 항목 선택

shuffle: 리스트를 제자리에서 재배열

양쪽 끝 포함

random: 0.0 ~ 1.0

uniform: 사용자 지정 범위

각 항목이 동일한 확률

중복 없음

원본 리스트를 수정

39.2) 날짜와 시간 다루기

datetime 모듈은 날짜, 시간, 시간 간격을 다루기 위한 클래스들을 제공합니다. 이는 스케줄링, 로깅, 지속 시간 계산, 그리고 언제 어떤 일이 발생했는지 추적해야 하는 모든 애플리케이션에 필수적입니다.

39.2.1) 현재 날짜와 시간 가져오기

datetime 클래스는 날짜와 시간 구성 요소를 모두 포함하는 특정 시점을 나타냅니다:

python
from datetime import datetime
 
# 현재 날짜와 시간 가져오기
now = datetime.now()
print(f"Current datetime: {now}")
# Output: Current datetime: 2026-01-02 14:30:45.123456
 
# 개별 구성 요소 접근
print(f"Year: {now.year}")      # Output: Year: 2026
print(f"Month: {now.month}")    # Output: Month: 1
print(f"Day: {now.day}")        # Output: Day: 2
print(f"Hour: {now.hour}")      # Output: Hour: 14
print(f"Minute: {now.minute}")  # Output: Minute: 30
print(f"Second: {now.second}")  # Output: Second: 45

시간 없이 날짜만 필요하다면 date 클래스를 사용하세요:

python
from datetime import date
 
# 오늘 날짜 가져오기
today = date.today()
print(f"Today: {today}")  # Output: Today: 2026-01-02
 
print(f"Year: {today.year}")    # Output: Year: 2026
print(f"Month: {today.month}")  # Output: Month: 1
print(f"Day: {today.day}")      # Output: Day: 2

39.2.2) 특정 날짜와 시간 만들기

특정 시점을 나타내는 datetimedate 객체를 만들 수 있습니다:

python
from datetime import datetime, date
 
# 특정 날짜 생성
birthday = date(1995, 7, 15)
print(f"Birthday: {birthday}")  # Output: Birthday: 1995-07-15
 
# 특정 datetime 생성
meeting = datetime(2026, 3, 15, 14, 30)  # 2026년 3월 15일 오후 2:30
print(f"Meeting: {meeting}")  # Output: Meeting: 2026-03-15 14:30:00

이는 마감일, 약속, 역사적 날짜, 또는 고정된 시점을 나타내는 데 유용합니다:

python
from datetime import date
 
# 프로젝트의 중요한 날짜들
project_start = date(2026, 1, 15)
project_end = date(2026, 6, 30)
 
print(f"Project duration: {project_start} to {project_end}")
# Output: Project duration: 2026-01-15 to 2026-06-30

39.2.3) timedelta로 시간 차이 계산하기

timedelta 클래스는 지속 시간, 즉 두 날짜 또는 시간 사이의 차이를 나타냅니다. 이를 사용해 경과 시간을 계산하거나 날짜에 시간을 더하거나 뺄 수 있습니다:

python
from datetime import date, timedelta
 
# 나이 계산
birth_date = date(1995, 7, 15)
today = date(2026, 1, 2)
age_delta = today - birth_date
 
print(f"Days since birth: {age_delta.days}")  # Output: Days since birth: 11128
print(f"Years (approximate): {age_delta.days // 365}")  # Output: Years (approximate): 30

한 날짜에서 다른 날짜를 빼면 timedelta 객체를 얻습니다. days 속성은 그 지속 시간의 일 수를 알려줍니다.

또한 특정 지속 시간을 나타내기 위해 timedelta 객체를 직접 만들 수도 있습니다:

python
from datetime import date, timedelta
 
# 날짜에 일수 더하기
today = date(2026, 1, 2)
one_week = timedelta(days=7)
next_week = today + one_week
 
print(f"Today: {today}")        # Output: Today: 2026-01-02
print(f"Next week: {next_week}")  # Output: Next week: 2026-01-09
 
# 날짜에서 일수 빼기
thirty_days_ago = today - timedelta(days=30)
print(f"30 days ago: {thirty_days_ago}")  # Output: 30 days ago: 2025-12-03

timedelta는 일, 초, 마이크로초, 밀리초, 분, 시간, 주를 나타낼 수 있습니다:

python
from datetime import datetime, timedelta
 
# 마감 시각 계산
now = datetime(2026, 1, 2, 14, 30)
deadline = now + timedelta(hours=48, minutes=30)
 
print(f"Current time: {now}")    # Output: Current time: 2026-01-02 14:30:00
print(f"Deadline: {deadline}")   # Output: Deadline: 2026-01-04 15:00:00
 
# 남은 시간 계산
time_left = deadline - now
print(f"Hours remaining: {time_left.total_seconds() / 3600}")  # Output: Hours remaining: 48.5

total_seconds() 메서드는 전체 지속 시간을 초 단위로 변환해 주며, 이를 다시 시간, 분 또는 다른 단위로 변환할 수 있습니다.

다음은 프로젝트 마일스톤을 계산하는 실용적인 예시입니다:

python
from datetime import date, timedelta
 
# 프로젝트 계획
project_start = date(2026, 1, 15)
sprint_duration = timedelta(weeks=2)
 
sprint_1_end = project_start + sprint_duration
sprint_2_end = sprint_1_end + sprint_duration
sprint_3_end = sprint_2_end + sprint_duration
 
print(f"Sprint 1: {project_start} to {sprint_1_end}")
# Output: Sprint 1: 2026-01-15 to 2026-01-29
print(f"Sprint 2: {sprint_1_end} to {sprint_2_end}")
# Output: Sprint 2: 2026-01-29 to 2026-02-12
print(f"Sprint 3: {sprint_2_end} to {sprint_3_end}")
# Output: Sprint 3: 2026-02-12 to 2026-02-26

39.2.4) 날짜와 시간 비교하기

date 및 datetime 객체는 표준 비교 연산자를 사용해 비교할 수 있습니다:

python
from datetime import date
 
# 날짜 비교
date1 = date(2026, 1, 15)
date2 = date(2026, 2, 20)
date3 = date(2026, 1, 15)
 
print(date1 < date2)   # Output: True
print(date1 == date3)  # Output: True
print(date2 > date1)   # Output: True

이는 마감일 확인, 날짜 범위 검증, 날짜 정렬 등에 유용합니다:

python
from datetime import date
 
# 날짜가 과거인지 확인
event_date = date(2025, 12, 25)
today = date(2026, 1, 2)
 
if event_date < today:
    print("This event has already passed")  # Output: This event has already passed
else:
    print("This event is upcoming")
 
# 날짜 리스트 정렬
important_dates = [
    date(2026, 3, 15),
    date(2026, 1, 10),
    date(2026, 2, 28)
]
 
important_dates.sort()
print("Dates in order:")  # Output: Dates in order:
for d in important_dates:
    print(f"  {d}")
# Output:
#   2026-01-10
#   2026-02-28
#   2026-03-15

39.2.5) strftime()로 날짜와 시간 형식 지정하기

strftime() 메서드(string format time)는 날짜와 시간을 형식화된 문자열로 변환합니다. 특수 코드를 사용해 형식을 지정합니다:

python
from datetime import datetime
 
now = datetime(2026, 1, 2, 14, 30, 45)
 
# 흔한 날짜 형식
print(now.strftime("%Y-%m-%d"))           # Output: 2026-01-02
print(now.strftime("%m/%d/%Y"))           # Output: 01/02/2026
print(now.strftime("%B %d, %Y"))          # Output: January 02, 2026
print(now.strftime("%A, %B %d, %Y"))      # Output: Friday, January 02, 2026
 
# 흔한 시간 형식
print(now.strftime("%H:%M:%S"))           # Output: 14:30:45
print(now.strftime("%I:%M %p"))           # Output: 02:30 PM
 
# 결합 형식
print(now.strftime("%Y-%m-%d %H:%M:%S"))  # Output: 2026-01-02 14:30:45
print(now.strftime("%B %d, %Y at %I:%M %p"))  # Output: January 02, 2026 at 02:30 PM

자주 쓰는 형식 코드:

코드설명예제
%Y세기를 포함한 연도2026
%m0으로 채운 월 (01-12)01
%d0으로 채운 일 (01-31)02
%B전체 월 이름January
%b축약된 월 이름Jan
%A전체 요일 이름Friday
%a축약된 요일 이름Fri
%H시간 24시간 형식 (00-23)14
%I시간 12시간 형식 (01-12)02
%M분 (00-59)30
%S초 (00-59)45
%pAM/PMPM

다음은 로그 항목을 만드는 실용적인 예시입니다:

python
from datetime import datetime
 
def log_event(message):
    """타임스탬프와 함께 이벤트를 로그로 기록합니다"""
    now = datetime.now()
    timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
    print(f"[{timestamp}] {message}")
 
log_event("User logged in")
# Output: [2026-01-02 14:30:45] User logged in
 
log_event("File uploaded successfully")
# Output: [2026-01-02 14:30:45] File uploaded successfully

39.2.6) strptime()로 문자열에서 날짜 파싱하기

strptime() 함수(string parse time)는 형식화된 문자열을 다시 datetime 객체로 변환합니다. 동일한 형식 코드를 사용하여 문자열을 어떻게 해석할지 Python에 알려줍니다:

python
from datetime import datetime
 
# 서로 다른 날짜 형식 파싱
date_str1 = "2026-01-15"
date1 = datetime.strptime(date_str1, "%Y-%m-%d")
print(f"Parsed: {date1}")  # Output: Parsed: 2026-01-15 00:00:00
 
date_str2 = "January 15, 2026"
date2 = datetime.strptime(date_str2, "%B %d, %Y")
print(f"Parsed: {date2}")  # Output: Parsed: 2026-01-15 00:00:00
 
# 시간 포함 datetime 파싱
datetime_str = "2026-01-15 14:30:00"
dt = datetime.strptime(datetime_str, "%Y-%m-%d %H:%M:%S")
print(f"Parsed: {dt}")  # Output: Parsed: 2026-01-15 14:30:00

이는 파일, 사용자 입력, 또는 외부 데이터 소스에서 날짜를 읽을 때 필수적입니다:

python
from datetime import datetime
 
# 사용자 입력 파싱
user_input = "03/15/2026"
try:
    event_date = datetime.strptime(user_input, "%m/%d/%Y")
    print(f"Event scheduled for: {event_date.strftime('%B %d, %Y')}")
    # Output: Event scheduled for: March 15, 2026
except ValueError:
    print("Invalid date format. Please use MM/DD/YYYY")

중요: 형식 문자열은 입력 문자열과 정확히 일치해야 하며, 그렇지 않으면 ValueError가 발생합니다:

python
from datetime import datetime
 
# 실패합니다 - 형식이 일치하지 않음
try:
    datetime.strptime("2026-01-15", "%m/%d/%Y")  # Wrong format
except ValueError as e:
    print(f"Error: {e}")
    # Output: Error: time data '2026-01-15' does not match format '%m/%d/%Y'

datetime 모듈

datetime.now: 현재 날짜/시간

date.today: 현재 날짜

datetime/date: 특정 날짜 생성

timedelta: 시간 간격

strftime: 문자열로 포맷

strptime: 문자열에서 파싱

날짜에 더하기/빼기

차이 계산

%Y, %m, %d, %H, %M, %S

형식이 정확히 일치해야 함

39.3) JSON 데이터 읽기와 쓰기

JSON(JavaScript Object Notation)은 구조화된 데이터를 저장하고 교환하기 위한 텍스트 형식입니다. 웹 API, 설정 파일, 프로그램 간 데이터 교환에서 가장 흔히 쓰이는 형식입니다. Python의 json 모듈을 사용하면 Python 데이터 구조와 JSON 텍스트 사이를 쉽게 변환할 수 있습니다.

39.3.1) JSON 구조 이해하기

JSON은 Python 딕셔너리와 리스트와 비슷해 보이지만, 몇 가지 차이점이 있습니다:

JSON이 지원하는 데이터 타입:

  • 객체(Python 딕셔너리와 유사): {"name": "Alice", "age": 30}
  • 배열(Python 리스트와 유사): [1, 2, 3, 4]
  • 문자열: "hello"(반드시 큰따옴표 사용)
  • 숫자: 42, 3.14
  • 불리언: true, false(소문자)
  • 널: null(Python의 None과 유사)

Python과의 주요 차이점:

  • JSON은 Python의 True/False/None 대신 true/false/null을 사용합니다
  • JSON 문자열은 작은따옴표가 아니라 큰따옴표("text")를 사용해야 합니다
  • JSON은 튜플, 집합, 사용자 정의 객체를 직접 지원하지 않습니다

다음은 JSON 데이터의 모습입니다:

json
{
    "name": "Alice Johnson",
    "age": 30,
    "email": "alice@example.com",
    "is_active": true,
    "scores": [85, 92, 78, 95],
    "address": {
        "street": "123 Main St",
        "city": "Springfield",
        "zip": "12345"
    }
}

참고: 이것은 순수 JSON 텍스트이며, Python 코드가 아닙니다. 소문자 true와 큰따옴표 사용에 주목하세요.

39.3.2) dumps()로 Python 데이터를 JSON으로 변환하기

dumps() 함수(dump string)는 Python 데이터 구조를 JSON 형식의 문자열로 변환합니다:

python
import json
 
student = {
    "name": "Alice Johnson",
    "age": 30,
    "email": "alice@example.com",
    "is_active": True,
    "scores": [85, 92, 78, 95]
}
 
# 딕셔너리를 JSON으로 변환
json_string = json.dumps(student)
print(json_string)
# Output: {"name": "Alice Johnson", "age": 30, "email": "alice@example.com", "is_active": true, "scores": [85, 92, 78, 95]}
 
print(type(json_string))  # Output: <class 'str'>

출력에서 Python의 True가 JSON의 true로 바뀐 것을 확인할 수 있습니다. dumps() 함수는 이런 변환을 자동으로 처리합니다.

더 읽기 쉬운 출력이 필요하다면 indent 매개변수를 사용하세요:

python
import json
 
student = {
    "name": "Alice Johnson",
    "age": 30,
    "scores": [85, 92, 78, 95]
}
 
# 들여쓰기를 사용해 보기 좋게 출력
json_string = json.dumps(student, indent=2)
print(json_string)
# Output:
# {
#   "name": "Alice Johnson",
#   "age": 30,
#   "scores": [
#     85,
#     92,
#     78,
#     95
#   ]
# }

indent 매개변수는 각 들여쓰기 레벨에 사용할 공백 수를 지정합니다. 이는 특히 복잡한 중첩 구조에서 JSON을 훨씬 읽기 쉽게 만들어 줍니다.

39.3.3) loads()로 JSON을 Python 데이터로 변환하기

loads() 함수(load string)는 JSON 형식의 문자열을 다시 Python 데이터 구조로 변환합니다:

python
import json
 
# JSON 문자열(웹 API에서 받을 수 있는 형태)
json_string = '{"name": "Bob Smith", "age": 25, "scores": [90, 88, 92]}'
 
# Python 딕셔너리로 변환
student = json.loads(json_string)
print(student)  # Output: {'name': 'Bob Smith', 'age': 25, 'scores': [90, 88, 92]}
print(type(student))  # Output: <class 'dict'>
 
# 일반적인 Python 딕셔너리처럼 데이터 접근
print(f"Name: {student['name']}")  # Output: Name: Bob Smith
print(f"Average score: {sum(student['scores']) / len(student['scores'])}")
# Output: Average score: 90.0

JSON의 true, false, null은 자동으로 Python의 True, False, None으로 변환됩니다:

python
import json
 
json_string = '{"active": true, "verified": false, "middle_name": null}'
data = json.loads(json_string)
 
print(data)  # Output: {'active': True, 'verified': False, 'middle_name': None}
print(type(data["active"]))  # Output: <class 'bool'>
print(type(data["middle_name"]))  # Output: <class 'NoneType'>

39.3.4) dump()로 JSON을 파일에 쓰기

dump() 함수는 Python 데이터를 JSON 형식으로 파일에 직접 씁니다:

python
import json
 
# 학생 기록
students = [
    {"name": "Alice", "age": 20, "gpa": 3.8},
    {"name": "Bob", "age": 22, "gpa": 3.5},
    {"name": "Charlie", "age": 21, "gpa": 3.9}
]
 
# JSON 파일로 쓰기
with open("students.json", "w") as file:
    json.dump(students, file, indent=2)
 
print("Data written to students.json")
# Output: Data written to students.json

이 코드를 실행하면 students.json 파일에는 다음이 들어 있습니다:

json
[
  {
    "name": "Alice",
    "age": 20,
    "gpa": 3.8
  },
  {
    "name": "Bob",
    "age": 22,
    "gpa": 3.5
  },
  {
    "name": "Charlie",
    "age": 21,
    "gpa": 3.9
  }
]

dumps() 대신 dump()를 사용하나요? dump() 함수는 파일에 직접 쓰기 때문에, 먼저 문자열로 변환한 다음 그 문자열을 쓰는 것보다 효율적입니다. 파일에는 dump()를 사용하고, JSON이 문자열로 필요할 때(예: 네트워크로 전송할 때)는 dumps()를 사용하세요.

39.3.5) load()로 JSON을 파일에서 읽기

load() 함수는 파일에서 JSON 데이터를 읽고 Python 데이터 구조로 변환합니다:

python
import json
 
# 앞에서 만든 JSON 파일에서 읽기
with open("students.json", "r") as file:
    students = json.load(file)
 
print(f"Loaded {len(students)} students")  # Output: Loaded 3 students
 
# 데이터로 작업
for student in students:
    print(f"{student['name']}: GPA {student['gpa']}")
# Output:
# Alice: GPA 3.8
# Bob: GPA 3.5
# Charlie: GPA 3.9

39.3.6) JSON 오류 처리하기

JSON을 다루다 보면 유효하지 않은 데이터를 만날 수 있습니다. 잠재적 오류를 항상 처리하세요:

python
import json
 
# 유효하지 않은 JSON - 닫는 따옴표가 없음
invalid_json = '{"name": "Alice", "age": 30'
 
try:
    data = json.loads(invalid_json)
except json.JSONDecodeError as e:
    print(f"Invalid JSON: {e}")
    # Output: Invalid JSON: Expecting ',' delimiter: line 1 column 28 (char 27)

이는 특히 데이터가 유효하다고 보장할 수 없는 외부 소스(파일, 웹 API, 사용자 입력)에서 JSON을 읽을 때 중요합니다:

python
import json
 
def load_config(filename):
    """오류 처리를 포함해 JSON 파일에서 설정을 로드합니다"""
    try:
        with open(filename, "r") as file:
            config = json.load(file)
            return config
    except FileNotFoundError:
        print(f"Config file '{filename}' not found")
        return None
    except json.JSONDecodeError as e:
        print(f"Invalid JSON in '{filename}': {e}")
        return None
 
# 설정 로드를 시도
config = load_config("config.json")
if config:
    print(f"Configuration loaded: {config}")
else:
    print("Using default configuration")

39.3.7) 실용적인 JSON 예시: 애플리케이션 상태 저장 및 로드

다음은 애플리케이션 데이터를 저장하고 로드하는 전체 예시입니다:

python
import json
 
def save_game_state(filename, player_data):
    """게임 상태를 JSON 파일에 저장합니다"""
    with open(filename, "w") as file:
        json.dump(player_data, file, indent=2)
    print(f"Game saved to {filename}")
 
def load_game_state(filename):
    """JSON 파일에서 게임 상태를 로드합니다"""
    try:
        with open(filename, "r") as file:
            player_data = json.load(file)
        print(f"Game loaded from {filename}")
        return player_data
    except FileNotFoundError:
        print("No saved game found")
        return None
 
# 게임 데이터
player = {
    "name": "Hero",
    "level": 5,
    "health": 85,
    "inventory": ["sword", "shield", "potion"],
    "position": {"x": 10, "y": 20}
}
 
# 게임 저장
save_game_state("savegame.json", player)
# Output: Game saved to savegame.json
 
# 나중에 게임 로드
loaded_player = load_game_state("savegame.json")
# Output: Game loaded from savegame.json
 
if loaded_player:
    print(f"Welcome back, {loaded_player['name']}!")
    print(f"Level: {loaded_player['level']}, Health: {loaded_player['health']}")
    # Output:
    # Welcome back, Hero!
    # Level: 5, Health: 85

json 모듈

dumps: Python → JSON 문자열

loads: JSON 문자열 → Python

dump: Python → JSON 파일

load: JSON 파일 → Python

indent 매개변수로 가독성 향상

타입 변환 처리

dumps + write보다 효율적

JSONDecodeError 처리

39.4) collections의 실용적인 컨테이너

collections 모듈은 Python의 내장 컨테이너(리스트, 딕셔너리, 집합)를 확장한 특수 컨테이너 타입을 제공합니다. 이 컨테이너들은 기본 자료구조만 사용하는 것보다 흔한 문제들을 더 우아하게 해결해 줍니다.

39.4.1) Counter로 항목 세기

Counter 클래스는 해시 가능한 객체를 세기 위해 설계되었습니다. 항목을 키로, 개수를 값으로 저장하는 딕셔너리의 하위 클래스입니다.

Counter가 입력으로 받는 것:

  • 모든 이터러블 (list, string, tuple 등)
  • 개수가 있는 다른 딕셔너리
  • 개수가 있는 키워드 인자

Counter가 저장하는 것:

  • 항목을 키로, 개수를 값으로 하는 딕셔너리
  • 예: Counter(['a', 'b', 'a']){'a': 2, 'b': 1}을 저장

일반 딕셔너리보다 나은 점:

  • 누락된 키에 대해 KeyError를 발생시키는 대신 0을 반환
  • most_common() 같은 counting 전용 메서드 제공
  • counter 간 산술 연산 지원

기본 사용법

python
from collections import Counter
 
# 단어의 문자 세기
word = "mississippi"
letter_counts = Counter(word)
print(letter_counts)
# Output: Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
 
# 딕셔너리처럼 개수에 접근
print(f"Number of 'i's: {letter_counts['i']}")
# Output: Number of 'i's: 4
 
print(f"Number of 'z's: {letter_counts['z']}")
# Output: Number of 'z's: 0 (누락된 키에 대해 0 반환, KeyError 없음!)

다양한 소스에서 Counter 생성

python
from collections import Counter
 
# list에서
votes = ["Alice", "Bob", "Alice", "Charlie", "Alice", "Bob", "Alice"]
vote_counts = Counter(votes)
print(vote_counts)
# Output: Counter({'Alice': 4, 'Bob': 2, 'Charlie': 1})
 
# 문자열에서 (각 문자를 셈)
letter_counts = Counter("hello")
print(letter_counts)
# Output: Counter({'l': 2, 'h': 1, 'e': 1, 'o': 1})
 
# dictionary에서
existing_counts = {'apple': 3, 'banana': 2}
fruit_counts = Counter(existing_counts)
print(fruit_counts)
# Output: Counter({'apple': 3, 'banana': 2})
 
# 키워드 인자에서
color_counts = Counter(red=5, blue=3, green=2)
print(color_counts)
# Output: Counter({'red': 5, 'blue': 3, 'green': 2})

most_common()으로 가장 흔한 항목 찾기

메서드 시그니처: most_common(n=None)

매개변수:

  • n (선택사항): 반환할 가장 흔한 항목의 수
  • n이 생략되거나 None이면 모든 항목 반환

반환값:

  • (항목, 개수) 튜플의 list
  • 개수로 정렬됨, 가장 높은 것이 먼저
  • 개수가 같으면 처음 발견된 순서대로
python
from collections import Counter
 
# 텍스트의 단어 빈도 분석
text = "the quick brown fox jumps over the lazy dog the fox"
words = text.split()
word_counts = Counter(words)
 
# 가장 흔한 3개 단어 가져오기
top_3 = word_counts.most_common(3)
print(top_3)
# Output: [('the', 3), ('fox', 2), ('quick', 1)]

Counter 산술 연산

Counter 객체에 덧셈, 뺄셈 및 기타 연산을 수행할 수 있습니다:

python
from collections import Counter
 
# 두 그룹의 항목 세기
group1 = Counter(["apple", "banana", "apple", "orange"])
print(group1)
# Output: Counter({'apple': 2, 'banana': 1, 'orange': 1})
 
group2 = Counter(["banana", "banana", "grape", "apple"])
print(group2)
# Output: Counter({'banana': 2, 'grape': 1, 'apple': 1})
 
# 개수 합치기
combined = group1 + group2
print(combined)
# Output: Counter({'apple': 3, 'banana': 3, 'orange': 1, 'grape': 1})
 
# 개수 빼기 (양수 결과만 유지)
difference = group1 - group2
print(difference)
# Output: Counter({'apple': 1, 'orange': 1})
# banana: 1 - 2 = -1 (음수, 제외됨)
# grape: group1에 없음, 제외됨

실용 예제: 학생 성적 분석

python
from collections import Counter
 
# 성적 분포
grades = ["A", "B", "A", "C", "B", "A", "B", "D", "A", "B", "C", "A"]
grade_counts = Counter(grades)
 
print(f"Total students: {len(grades)}")
# Output: Total students: 12
 
print("\nGrade Distribution:")
for grade, count in grade_counts.most_common():
    percentage = (count / len(grades)) * 100
    bar = "█" * count
    print(f"  {grade}: {count} students ({percentage:4.1f}%) {bar}")
# Output:
# Grade Distribution:
#   A: 5 students (41.7%) █████
#   B: 4 students (33.3%) ████
#   C: 2 students (16.7%) ██
#   D: 1 students ( 8.3%) █

39.4.2) defaultdict를 사용한 기본값이 있는 딕셔너리

defaultdict 클래스는 존재하지 않는 키에 접근할 때 자동으로 기본값으로 항목을 생성하는 딕셔너리 서브클래스입니다. 이를 통해 키가 존재하는지 확인할 필요가 없어집니다.

defaultdict가 입력으로 받는 것:

  • 기본 팩토리(default factory) 함수 (필수): 누락된 키에 대한 기본값을 반환하는 호출 가능한(callable) 객체
  • 일반 dict가 받는 모든 인수 (키-값 쌍, 다른 딕셔너리, 키워드 인수)

일반 딕셔너리 대비 주요 장점:

  • 키를 사용하기 전에 존재 여부를 확인할 필요가 없음
  • 누락된 키를 자동으로 기본값으로 초기화
  • 그룹화, 카운팅, 누적 연산에서 더 깔끔하고 읽기 쉬운 코드

기본 팩토리(Default Factory) 이해하기

defaultdict를 생성할 때 기본 팩토리를 제공해야 합니다. 이는 인수를 받지 않고 기본값을 반환하는 호출 가능한 객체(함수)입니다. 일반적인 기본 팩토리:

  • int - 0을 반환 (카운팅에 유용)
  • list - []를 반환 (항목 그룹화에 유용)
  • set - set()을 반환 (고유 항목 수집에 유용)
  • str - ''를 반환 (문자열 연결에 유용)
  • lambda: value - 사용자 정의 기본값 반환
python
from collections import defaultdict
 
# 다양한 기본 팩토리
counts = defaultdict(int)        # 누락된 키는 0 반환
groups = defaultdict(list)       # 누락된 키는 [] 반환
unique = defaultdict(set)        # 누락된 키는 set() 반환
custom = defaultdict(lambda: "N/A")  # 누락된 키는 "N/A" 반환
 
# 누락된 키로 테스트
print(counts['missing'])     # 출력: 0
print(groups['missing'])     # 출력: []
print(unique['missing'])     # 출력: set()
print(custom['missing'])     # 출력: N/A

기본 사용법: defaultdict로 카운팅하기

카운팅을 위한 일반 딕셔너리 vs defaultdict 비교:

python
from collections import defaultdict
 
word = "mississippi"
 
# 일반 딕셔너리 - 키 존재 여부 확인 필요
regular_dict = {}
for letter in word:
    if letter not in regular_dict:
        regular_dict[letter] = 0
    regular_dict[letter] += 1
 
print(regular_dict)
# 출력: {'m': 1, 'i': 4, 's': 4, 'p': 2}
 
# defaultdict - 자동으로 기본값으로 항목 생성
letter_counts = defaultdict(int)  # int()는 0을 반환
for letter in word:
    letter_counts[letter] += 1  # 키 존재 여부 확인 불필요!
 
print(dict(letter_counts))
# 출력: {'m': 1, 'i': 4, 's': 4, 'p': 2}

동작 원리:

  1. 새로운 문자에 대해 letter_counts[letter]에 접근하면, defaultdictint()를 호출하여 0을 반환
  2. 키가 값 0으로 생성되고, += 11이 됨
  3. 기존 키의 경우 일반 딕셔너리처럼 동작

defaultdict(list)로 항목 그룹화하기

항목을 카테고리별로 그룹화하는 일반적인 사용 사례:

python
from collections import defaultdict
 
students = [
    ("Alice", "A"),
    ("Bob", "B"),
    ("Charlie", "A"),
    ("Diana", "C"),
    ("Eve", "B"),
    ("Frank", "A")
]
 
# 학생을 성적별로 그룹화
# defaultdict 사용 - 깔끔하고 간단
students_by_grade = defaultdict(list)
for name, grade in students:
    students_by_grade[grade].append(name)
 
print(dict(students_by_grade))
# 출력: {'A': ['Alice', 'Charlie', 'Frank'], 'B': ['Bob', 'Eve'], 'C': ['Diana']}
 
# 아직 존재하지 않는 성적에 접근
print(students_by_grade["D"])  # 출력: [] (KeyError가 아닌 빈 리스트!)

동작 원리:

  1. 새로운 성적에 대해 students_by_grade[grade]에 접근하면, defaultdictlist()를 호출하여 []를 반환
  2. 키가 빈 리스트로 생성되고, .append(name)으로 첫 번째 학생이 추가됨
  3. 기존 성적의 경우 기존 리스트에 추가만 함

기존 딕셔너리로부터 defaultdict 생성하기

기존 데이터로 defaultdict를 초기화할 수 있습니다:

python
from collections import defaultdict
 
# 기존 카운트로 시작
existing_data = {'apple': 5, 'banana': 3}
 
# 기존 딕셔너리로부터 defaultdict 생성
fruit_counts = defaultdict(int, existing_data)
 
# 더 많은 카운트 추가
fruit_counts['apple'] += 2     # 5 + 2 = 7
fruit_counts['orange'] += 1    # 0 + 1 = 1 (새 키, 0부터 시작)
 
print(dict(fruit_counts))
# 출력: {'apple': 7, 'banana': 3, 'orange': 1}

사용자 정의 기본 팩토리

호출 가능한 모든 객체를 기본 팩토리로 제공할 수 있습니다:

python
from collections import defaultdict
 
# 사용자 정의 기본값을 위해 lambda 사용
page_views = defaultdict(lambda: {'views': 0, 'unique': 0})
 
page_views['home']['views'] = 100
page_views['home']['unique'] = 75
 
print(page_views['home'])
# 출력: {'views': 100, 'unique': 75}
 
print(page_views['about'])  # 새 키는 기본 딕셔너리를 받음
# 출력: {'views': 0, 'unique': 0}

중요 참고사항

키 접근 vs. 키 확인:

python
from collections import defaultdict
 
counts = defaultdict(int)
 
# 누락된 키에 접근하면 키가 생성됨
value = counts['missing']  # 'missing'을 값 0으로 생성
print('missing' in counts)  # 출력: True
 
# 생성 없이 확인하려면 'in' 또는 .get() 사용
counts2 = defaultdict(int)
print('missing' in counts2)      # 출력: False (키를 생성하지 않음)
print(counts2.get('missing'))    # 출력: None (키를 생성하지 않음)

39.5) (선택) 유용한 반복 도구

itertools 모듈은 효율적인 이터레이터(iterator)를 만들기 위한 함수들을 제공합니다. 이 도구들은 큰 중간 리스트를 만들지 않고도 강력한 방식으로 시퀀스를 다루게 해 줍니다.

39.5.1) chain()으로 이터러블 이어붙이기

chain() 함수는 여러 이터러블을 순서대로 각 이터러블의 요소를 생성하는 단일 이터레이터로 결합합니다.

chain()이 받는 것:

  • 여러 이터러블(list, tuple, string 등)을 개별 인자로

chain()이 반환하는 것:

  • 첫 번째 이터러블의 모든 요소를 생성한 다음, 두 번째 이터러블의 모든 요소를 생성하는 식으로 진행되는 이터레이터

주요 장점:

  • +로 연결하는 것보다 메모리 효율적 (중간 list를 생성하지 않음)
  • list뿐만 아니라 모든 이터러블과 작동
python
from itertools import chain
 
# 여러 리스트 결합
list1 = [1, 2, 3]
list2 = [4, 5, 6]
list3 = [7, 8, 9]
 
combined = chain(list1, list2, list3)
print(list(combined))  # Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]

이는 특히 큰 시퀀스에서 +로 리스트를 이어붙이는 것보다 메모리 효율적입니다:

python
from itertools import chain
 
# 큰 결합 리스트를 만들지 않고 여러 데이터 소스 처리
students_class_a = ["Alice", "Bob", "Charlie"]
students_class_b = ["Diana", "Eve", "Frank"]
students_class_c = ["Grace", "Henry", "Iris"]
 
# 결합 리스트를 만들지 않고 모든 학생 순회
for student in chain(students_class_a, students_class_b, students_class_c):
    print(f"Processing: {student}")
# Output:
# Processing: Alice
# Processing: Bob
# Processing: Charlie
# Processing: Diana
# Processing: Eve
# Processing: Frank
# Processing: Grace
# Processing: Henry
# Processing: Iris

서로 다른 타입의 이터러블도 이어붙일 수 있습니다:

python
from itertools import chain
 
# 리스트, 튜플, 문자열을 이어붙이기
numbers = [1, 2, 3]
letters = ("a", "b", "c")
word = "xyz"
 
combined = chain(numbers, letters, word)
print(list(combined))  # Output: [1, 2, 3, 'a', 'b', 'c', 'x', 'y', 'z']

39.5.2) cycle()로 요소 반복하기

cycle() 함수는 이터러블의 요소를 반복적으로 순환하는 무한 이터레이터를 생성합니다.

cycle()이 받는 것:

  • 단일 이터러블(list, tuple, string 등)

cycle()이 반환하는 것:

  • 이터러블의 요소를 반복적으로 생성하는 무한 이터레이터
  • 끝에 도달하면 처음부터 다시 시작

주요 특징:

  • 무한 이터레이터 생성 - 스스로 멈추지 않음
  • 중지 조건 (counter, break, 또는 zip())과 함께 사용해야 함
  • 메모리 효율적: 데이터 복사본을 생성하지 않음
python
from itertools import cycle
 
# 색상의 무한 반복 생성
colors = cycle(["red", "green", "blue"])
 
# 처음 10개 색 가져오기
for i, color in enumerate(colors):
    if i >= 10:
        break
    print(f"Item {i}: {color}")
# Output:
# Item 0: red
# Item 1: green
# Item 2: blue
# Item 3: red
# Item 4: green
# Item 5: blue
# Item 6: red
# Item 7: green
# Item 8: blue
# Item 9: red

경고: cycle()은 무한 이터레이터를 만듭니다. 반드시 종료 조건(카운터나 break 문 등)과 함께 사용해야 하며, 그렇지 않으면 무한 반복문이 됩니다.

실용적인 사용 사례로는 값을 번갈아 가며 사용하는 경우가 있습니다:

python
from itertools import cycle
 
# 테이블 행 배경색을 두 색으로 번갈아 적용
row_colors = cycle(["white", "lightgray"])
 
rows = ["Row 1", "Row 2", "Row 3", "Row 4", "Row 5"]
for row, color in zip(rows, row_colors):
    print(f"{row}: background-color: {color}")
# Output:
# Row 1: background-color: white
# Row 2: background-color: lightgray
# Row 3: background-color: white
# Row 4: background-color: lightgray
# Row 5: background-color: white

여기서는 37장에서 배운 zip()을 사용해 각 행과 색을 짝지었습니다. cycle() 이터레이터는 필요에 따라 색을 자동으로 반복합니다.

39.5.3) chain()과 cycle() 결합하기

더 복잡한 패턴을 위해 itertools 함수들을 결합할 수 있습니다:

python
from itertools import chain, cycle
 
# 여러 시퀀스를 순환하는 패턴 만들기
pattern1 = [1, 2, 3]
pattern2 = [10, 20]
 
# 패턴을 chain으로 이어붙인 다음, 결과를 cycle로 반복
combined_pattern = cycle(chain(pattern1, pattern2))
 
# 처음 12개 값 가져오기
for i, value in enumerate(combined_pattern):
    if i >= 12:
        break
    print(value, end=" ")
# Output: 1 2 3 10 20 1 2 3 10 20 1 2
 
print()  # Newline

이렇게 하면 1, 2, 3, 10, 20, 1, 2, 3, 10, 20, ... 같은 반복 패턴이 만들어집니다.

다음은 순환 스케줄을 만드는 실용적인 예시입니다:

python
from itertools import cycle
 
# 팀원 순환 스케줄 생성
team_members = ["Alice", "Bob", "Charlie"]
schedule = cycle(team_members)
 
# 작업을 팀원에게 순환 배정
tasks = [
    "Review code",
    "Write tests",
    "Update documentation",
    "Fix bug #123",
    "Implement feature X",
    "Deploy to staging"
]
 
print("Task Assignments:")
for task, assignee in zip(tasks, schedule):
    print(f"  {assignee}: {task}")
# Output:
# Task Assignments:
#   Alice: Review code
#   Bob: Write tests
#   Charlie: Update documentation
#   Alice: Fix bug #123
#   Bob: Implement feature X
#   Charlie: Deploy to staging

itertools 모듈

chain: 이터러블 결합

cycle: 무한 반복

+보다 메모리 효율적

서로 다른 타입에서도 동작

무한 이터레이터 생성

항상 종료 조건과 함께 사용

교대 패턴에 유용


이 장에서는 Python의 기능을 확장해 주는 필수 표준 라이브러리 모듈 다섯 가지를 살펴보았습니다:

  • random: 난수를 생성하고, 무작위 선택을 하고, 시퀀스를 섞습니다—시뮬레이션, 게임, 테스트에 필수
  • datetime: 날짜, 시간, 기간을 다룹니다—나이 계산, 이벤트 예약, 타임스탬프 형식 지정
  • json: 범용 JSON 형식을 사용해 다른 프로그램과 데이터를 교환합니다—애플리케이션 상태 저장, 웹 API 작업, 설정 저장
  • collections: 카운팅을 위한 Counter, 키 자동 생성을 위한 defaultdict 같은 특수 컨테이너를 사용합니다
  • itertools: chain()으로 시퀀스를 결합하고 cycle()로 패턴을 반복하는 등 효율적인 이터레이터를 만듭니다

이 모듈들은 Python 표준 라이브러리의 일부이므로 언제나 사용할 수 있고, 충분히 테스트되어 있으며, 흔한 프로그래밍 문제를 우아하게 해결해 줍니다. 더 복잡한 프로그램을 만들수록 이런 도구들을 자주 찾게 될 것입니다. 이는 “batteries included”라는 Python 철학을 보여줍니다. 즉, 일상적인 프로그래밍 작업을 위한 강력하고 즉시 사용할 수 있는 해결책을 제공한다는 뜻입니다.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai