문서 읽는 데 59분 · G1

G-1: 유용한 표준 라이브러리

목차 29
전체 22강 중 21강 · 파이썬 기초
난이도 · 입문

ℹ️프로그래밍 경험이 없어도 시작할 수 있는 파이썬 입문 — ‘개발 입문’을 먼저 들으면 더 수월해요. AI·데이터 분석으로 이어지는 출발점이에요.

안녕하세요, 홍순구 튜터입니다. 지난 시간엔 async/await 로 여러 일을 동시에 기다리는 비동기를 다뤘죠. 그러면서 제가 마지막에 "이제 문법을 다 배웠으니 그 문법으로 만든 도구상자를 채울 차례" 라고 했어요. 오늘이 바로 그 도구상자를 여는 날입니다.

파이썬은 "배터리 포함(batteries included)" 이라고 불려요. 장난감을 샀는데 건전지까지 들어 있는 것처럼, 파이썬을 설치하면 자주 쓰는 도구가 한가득 따라와요. 설치(uv add) 도 필요 없이 import 한 줄이면 바로 꺼내 써요. 지난 C-3 에서 mathrandom 을 잠깐 맛보면서 "자주 쓰는 건 뒤쪽에서 더 깊이" 라고 약속했는데, 그 약속을 오늘 지킵니다.

오늘은 인기 해시태그를 세는 도구, 날짜와 시간을 다루는 도구, 글에서 원하는 패턴을 찾아내는 도구를 골라 익혀요. 전부 인스타그램에서 실제로 쓰일 법한 것들이에요.

텍스트
 오늘의 여정 — 파이썬에 딸려 오는 도구상자 열기

  ① Counter        몇 번 나왔나 — 인기 해시태그 빈도
  ② defaultdict    없는 키에 기본값 — 작성자별로 묶기
  ③ deque          양쪽 끝이 빠른 큐 — 최근 본 N개
  ④ itertools      반복을 엮고·자르고·조합하고
  ⑤ datetime       날짜와 시간, 그리고 시간대
  ⑥ re             글에서 패턴 찾기 (정규식)
  ⑦ random         무작위로 뽑고 섞기
  ⑧ 종합           도구 총동원 — 피드 인사이트

💡 오늘 수업의 핵심 — "외워서 만들 필요 없다. 잘 만든 도구를 꺼내 쓴다"

오늘 배울 것들은 사실 우리가 직접 만들 수도 있어요. 빈도를 세는 것도, 날짜를 계산하는 것도요. 하지만 이미 파이썬을 만든 사람들이 빠르고 정확하게 만들어 둔 도구가 있는데 굳이 다시 만들 이유가 없죠. 오늘의 목표는 "이런 도구가 있다" 를 알고, 필요할 때 꺼내 쓰는 감각을 익히는 거예요.

🎯 학습 목표

  • collectionsCounter·defaultdict·deque 로 빈도·그룹·큐를 간단히 다뤄요.
  • itertools·datetime·re·random 으로 반복·시간·패턴·무작위를 처리해요.
  • 표준 라이브러리를 조합해 "피드에서 인기 해시태그를 뽑는" 작은 분석 기능을 완성해요.

Step 1: "몇 번 나왔나 — Counter"

인스타그램에서 "이번 주 인기 해시태그" 를 뽑으려면 어떻게 해야 할까요? 게시물마다 달린 해시태그를 다 모아서, 어떤 게 몇 번 나왔는지 세야 해요.

지난 B-2 에서 set 으로 중복은 없앴지만, 그건 "어떤 종류가 있나" 만 알려줬지 "몇 번" 은 못 셌어요. 직접 세려면 빈 딕셔너리를 만들고, 태그가 있으면 +1, 없으면 1 로 시작하고… 꽤 번거롭죠. 이걸 한 방에 해주는 도구가 collectionsCounter 예요.

Python
# src/g_stdlib/g1_counter.py
from collections import Counter


def count_hashtags(tags: list[str]) -> Counter:
    """해시태그 리스트에서 각 태그가 몇 번 나왔는지 센다."""
    return Counter(tags)


def top_hashtags(tags: list[str], n: int) -> list[tuple[str, int]]:
    """가장 많이 나온 해시태그 n개를 (태그, 횟수)로 돌려준다."""
    return Counter(tags).most_common(n)

리스트를 Counter() 에 넣기만 하면, 각 항목이 몇 번 나왔는지 알아서 다 세줘요. 그리고 most_common(n) 은 가장 많이 나온 것부터 n개를 뽑아줘요. 인기 순위가 바로 나오는 거죠.

Python
tags = ["#일상", "#카페", "#일상", "#여행", "#카페", "#일상"]

counts = count_hashtags(tags)
print(counts)
print(counts["#일상"])     # 3
print(counts["#없는태그"])  # 0  — 없는 키도 KeyError 없이 0

print(top_hashtags(tags, 2))  # [('#일상', 3), ('#카페', 2)]

실행하면 이렇게 나와요.

텍스트
Counter({'#일상': 3, '#카페': 2, '#여행': 1})
3
0
[('#일상', 3), ('#카페', 2)]

세 가지가 눈에 띄어요. 첫째, Counter 를 출력하면 횟수가 많은 순서대로 정리돼서 보여요. 둘째, counts["#없는태그"] 처럼 한 번도 안 나온 태그를 물어봐도 에러 없이 0 을 돌려줘요. 지난 B-2 에서 일반 딕셔너리는 없는 키를 물으면 KeyError 가 났던 것과 다르죠. 셋째, most_common(2) 한 줄로 상위 2개 순위가 깔끔하게 나와요.

Counter 끼리 더할 수도 있어요. 월요일에 모은 태그와 화요일에 모은 태그를 합쳐 한 주 통계를 내는 식이죠.

Python
monday = Counter(["#일상", "#카페"])
tuesday = Counter(["#일상", "#여행"])
print(monday + tuesday)
텍스트
Counter({'#일상': 2, '#카페': 1, '#여행': 1})

같은 태그(#일상)는 횟수가 합쳐지고, 나머지는 그대로 모여요.

💡 한 줄 정리

Counter(리스트) 로 빈도를 한 방에 세고, most_common(n) 으로 가장 많이 나온 n개를 뽑아요. 없는 키를 물어도 0 이라 안전해요.

🙋 학생 질문 — "튜터님, Counter 랑 그냥 딕셔너리랑 뭐가 달라요?"

좋은 질문이에요. 사실 Counter 는 딕셔너리의 한 종류예요. 딕셔너리가 할 수 있는 건 Counter 도 다 할 수 있어요. counts["#일상"] 처럼 키로 값을 꺼내는 것도 똑같죠.

다른 점은 "세는 일에 특화" 됐다는 거예요. 일반 딕셔너리로 빈도를 세려면 "키가 있나 확인 → 있으면 +1, 없으면 1" 을 매번 직접 써야 해요. Counter 는 리스트만 넘기면 그걸 알아서 해주고, 없는 키엔 0, 순위를 뽑는 most_common 까지 얹어줘요. "빈도를 센다" 는 의도가 코드에 바로 드러나는 거예요.


Step 2: "없으면 기본값 — defaultdict"

이번엔 작성자별로 게시물을 묶어 볼게요. [(작성자, 캡션), ...] 목록을 받아서, 같은 사람이 쓴 글끼리 모으는 거예요.

일반 딕셔너리로 하면 문제가 하나 생겨요. 어떤 작성자가 처음 등장할 때는 그 사람의 리스트가 아직 없어서, 곧장 append 하면 KeyError 가 나요. 그래서 "키가 있나 먼저 확인하고, 없으면 빈 리스트부터 만들고…" 를 매번 해야 하죠. 이 귀찮음을 없애주는 게 defaultdict 예요. 이름 그대로 "기본값(default)이 있는 딕셔너리" 예요.

Python
# src/g_stdlib/g1_defaultdict.py
from collections import defaultdict


def group_by_author(posts: list[tuple[str, str]]) -> defaultdict:
    """(작성자, 캡션) 목록을 작성자별 캡션 리스트로 묶는다."""
    grouped = defaultdict(list)
    for author, caption in posts:
        grouped[author].append(caption)
    return grouped

defaultdict(list) 라고 만들면, 없는 키를 건드리는 순간 자동으로 빈 리스트([])가 생겨요. 그래서 "있나 없나" 확인 없이 바로 append 할 수 있죠. list 대신 int 를 넣으면 없는 키의 기본값이 0 이 돼서 개수 세기에 좋아요.

Python
posts = [
    ("jaehoon", "첫 게시물"),
    ("minji", "카페 왔어요"),
    ("jaehoon", "두 번째"),
    ("jaehoon", "세 번째"),
]

grouped = group_by_author(posts)
print(grouped["jaehoon"])   # ['첫 게시물', '두 번째', '세 번째']
print(grouped["없는사람"])   # []  — 없는 키를 읽기만 해도 빈 리스트가 생김
텍스트
['첫 게시물', '두 번째', '세 번째']
[]

jaehoon 이 쓴 글 세 개가 한 리스트로 묶였어요. 재미있는 건 grouped["없는사람"] 이에요. 한 번도 등장하지 않은 키인데도 에러 대신 빈 리스트가 나와요. defaultdict 가 "없으면 기본값을 즉석에서 만들어" 주기 때문이에요.

개수만 세고 싶을 땐 defaultdict(int) 를 쓰면 돼요.

Python
def count_by_author(posts: list[tuple[str, str]]) -> defaultdict:
    """작성자별 게시물 개수를 센다."""
    counts = defaultdict(int)
    for author, _ in posts:
        counts[author] += 1
    return counts

counts[author] += 1 한 줄이 깔끔하게 도는 건, 처음 보는 작성자의 기본값이 0 이라 곧장 +1 이 되기 때문이에요.

💡 한 줄 정리

defaultdict(list)defaultdict(int) 는 없는 키에 기본값([]0)을 자동으로 만들어줘서, "있나 없나" 확인 없이 바로 묶거나 셀 수 있어요.

🙋 학생 질문 — "튜터님, defaultdict 없이 그냥 딕셔너리로는 못 하나요?"

물론 할 수 있어요. 일반 딕셔너리에도 setdefault 라는 방법이 있고, "키가 있나 if 로 확인하고 없으면 만들기" 도 가능해요. 결과는 똑같아요.

다만 그렇게 하면 매번 "이 키가 처음인가?" 를 신경 써야 해서 코드가 길어지고, 깜빡하면 KeyError 로 이어져요. defaultdict 는 그 신경을 처음 한 번(defaultdict(list))에 몰아넣어요. 그다음부턴 "그냥 묶는다" 에만 집중하면 되죠. 같은 일을 더 적은 실수로 하게 해주는 도구예요.


Step 3: "양쪽 끝이 빠른 큐 — deque"

이번엔 "최근 본 게시물 5개" 같은 기능을 만들어 볼게요. 새 게시물을 볼 때마다 목록 끝에 추가하고, 6개째가 되면 가장 오래된 걸 앞에서 빼는 식이에요.

여기서 "큐(queue)" 라는 개념을 잠깐 짚을게요. 큐는 줄 서기예요. 먼저 들어온 사람이 먼저 나가죠(먼저 온 순서대로). 리스트로도 비슷하게 흉내 낼 수 있지만, 리스트는 맨 앞에서 빼는 일(앞사람이 나가는 일)이 느려요. 뒤쪽 항목들을 한 칸씩 당겨야 하거든요. collectionsdeque 는 양쪽 끝에서 넣고 빼는 일이 둘 다 빨라요. 이름은 "double-ended queue(양끝 큐)" 의 줄임말이에요.

Python
# src/g_stdlib/g1_deque.py
from collections import deque


def recent_views(history: list[str], limit: int) -> list[str]:
    """본 게시물 기록에서 최근 limit개만 남긴다 (오래된 건 자동으로 밀려남)."""
    recent = deque(maxlen=limit)
    for post in history:
        recent.append(post)
    return list(recent)

먼저 양끝에서 넣고 빼는 모습을 볼게요.

Python
queue = deque(["a", "b", "c"])
queue.append("d")       # 오른쪽 끝에 추가
queue.appendleft("z")   # 왼쪽 끝에 추가
print(queue)            # deque(['z', 'a', 'b', 'c', 'd'])

print(queue.popleft())  # z  — 왼쪽 끝을 꺼냄
print(queue.pop())      # d  — 오른쪽 끝을 꺼냄
print(queue)            # deque(['a', 'b', 'c'])
텍스트
deque(['z', 'a', 'b', 'c', 'd'])
z
d
deque(['a', 'b', 'c'])

append 는 오른쪽, appendleft 는 왼쪽에 넣어요. pop 은 오른쪽, popleft 는 왼쪽에서 꺼내고요. 리스트엔 appendleft·popleft 가 없는데, deque 는 양쪽을 똑같이 빠르게 다뤄요.

진짜 편한 건 maxlen 이에요. 최대 개수를 정해 두면, 그 수를 넘는 순간 반대쪽 끝에서 오래된 게 자동으로 밀려나요.

Python
viewed = ["1번", "2번", "3번", "4번", "5번"]
print(recent_views(viewed, 3))  # ['3번', '4번', '5번']
텍스트
['3번', '4번', '5번']

5개를 넣었는데 maxlen=3 이라, 가장 최근 3개(3번·4번·5번)만 남았어요. "최근 N개만 유지" 를 직접 길이 검사할 필요 없이 deque 가 알아서 해주는 거죠.

💡 한 줄 정리

deque 는 양쪽 끝에서 넣고 빼는 일이 빠르고(appendleft/popleft), maxlen 으로 "최근 N개만 유지" 를 자동으로 처리해요.

🙋 학생 질문 — "튜터님, 리스트로도 되는데 왜 굳이 deque 를 써요?"

짧은 목록이라면 리스트로도 충분해요. 차이가 드러나는 건 두 가지 상황이에요.

하나는 "맨 앞에서 자주 빼는" 경우예요. 리스트의 pop(0) 은 뒤 항목을 전부 한 칸씩 당겨야 해서, 데이터가 많아지면 느려져요. deque.popleft 는 그런 부담이 없어요. 다른 하나는 "최근 N개만 유지" 예요. 리스트로 하려면 추가할 때마다 길이를 재고 넘으면 잘라야 하는데, deque(maxlen=N) 은 그 일을 알아서 해줘요. "양끝을 자주 다룬다" 싶으면 deque 를 떠올리면 돼요.


Step 4: "반복을 엮고·자르고·조합하고 — itertools"

itertools 는 "반복(iteration)을 다루는 도구 모음" 이에요. 그중 자주 쓰는 세 가지만 골라 볼게요. 여러 개를 이어 붙이는 chain, 앞에서 몇 개만 잘라내는 islice, 짝을 만드는 combinations 예요.

Python
# src/g_stdlib/g1_itertools.py
from itertools import chain, islice, combinations


def merge_feeds(*feeds: list[str]) -> list[str]:
    """여러 피드를 하나로 이어 붙인다."""
    return list(chain(*feeds))


def follow_pairs(members: list[str]) -> list[tuple[str, str]]:
    """회원들로 만들 수 있는 모든 짝(순서 무관)을 만든다 — 맞팔 추천용."""
    return list(combinations(members, 2))

먼저 chain 은 여러 리스트를 하나로 죽 이어 줘요. 팔로잉 피드와 탐색 피드를 합쳐 한 화면에 보여주는 식이죠.

Python
following = ["@minji 글", "@jaehoon 글"]
explore = ["#여행 추천글", "#카페 추천글"]
print(merge_feeds(following, explore))
텍스트
['@minji 글', '@jaehoon 글', '#여행 추천글', '#카페 추천글']

islice 는 "앞에서 n개만" 잘라내요. 특히 지난 F-2 에서 만든 제너레이터처럼 "끝이 없는" 것에서도 앞부분만 안전하게 꺼낼 수 있어요. 끝없이 1, 2, 3… 을 세는 제너레이터에서 앞 5개만 가져와 볼게요.

Python
def numbers():
    """1부터 끝없이 세는 제너레이터 (F-2 에서 본 그 모양)."""
    n = 1
    while True:
        yield n
        n += 1


def first_n(iterable, n: int) -> list:
    """어떤 반복 가능한 것이든 앞에서 n개만 꺼낸다 (제너레이터도 OK)."""
    return list(islice(iterable, n))
Python
print(first_n(numbers(), 5))  # [1, 2, 3, 4, 5]
텍스트
[1, 2, 3, 4, 5]

numbers() 는 멈추라는 말이 없으면 영원히 도는데, islice 가 "앞에서 5개만" 잘라서 끝을 만들어 줬어요. 끝없는 흐름에서 필요한 만큼만 떠오는 거죠.

combinations 는 모든 짝을 만들어요. 회원 셋 중에서 맞팔을 추천할 때 "누구와 누구" 짝을 전부 뽑는 식이에요.

Python
print(follow_pairs(["민지", "재훈", "승우"]))
텍스트
[('민지', '재훈'), ('민지', '승우'), ('재훈', '승우')]

세 명에서 두 명씩 고르는 모든 짝이 나왔어요. 순서는 따지지 않아서 (민지, 재훈)(재훈, 민지) 를 같은 짝으로 봐요. 그래서 중복 없이 세 가지만 나오죠.

💡 한 줄 정리

itertoolschain 은 여러 개를 이어 붙이고, islice 는 (끝없는 것에서도) 앞 n개만 잘라내고, combinations 는 모든 짝을 만들어요.

🙋 학생 질문 — "튜터님, chain 이랑 리스트 + 리스트 더하기랑 뭐가 달라요?"

결과만 보면 거의 같아요. following + explore 도 두 리스트를 이어 주니까요.

차이는 "언제 실제로 합치느냐" 예요. + 는 그 자리에서 두 리스트를 합친 새 리스트를 통째로 만들어요. chain 은 지난 F-2 의 제너레이터처럼 "필요할 때 하나씩" 흘려보내요. 그래서 아주 큰 데이터나 끝없는 흐름을 이어 붙일 때, chain 은 전부를 메모리에 펼치지 않아 부담이 적어요. 작은 리스트 두어 개면 + 로 충분하고, 크거나 끝없는 걸 다룰 땐 chain 이 빛나요.


Step 5: "날짜와 시간, 그리고 시간대"

게시물엔 항상 "언제 올렸나" 가 따라붙어요. "3시간 전", "며칠 전" 같은 표시도 결국 시각 계산이죠. 파이썬은 datetime 으로 날짜와 시간을 다뤄요.

먼저 개념 하나만 짚을게요. 컴퓨터는 시간을 그냥 글자가 아니라 datetime 이라는 "시각 값" 으로 다뤄요. 이 값끼리는 빼기도 되고(차이를 구함), 일정 기간을 더하거나 뺄 수도 있어요. 기간을 나타내는 값은 timedelta 라고 불러요.

Python
# src/g_stdlib/g1_datetime.py
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo


def format_posted_at(dt: datetime) -> str:
    """게시 시각을 'YYYY-MM-DD HH:MM' 형태 문자열로 바꾼다."""
    return dt.strftime("%Y-%m-%d %H:%M")


def time_ago(posted: datetime, now: datetime) -> str:
    """게시 시각과 현재 시각의 차이를 'N시간 전' 또는 'N분 전'으로."""
    diff = now - posted
    hours = diff.total_seconds() // 3600
    if hours >= 1:
        return f"{int(hours)}시간 전"
    minutes = diff.total_seconds() // 60
    return f"{int(minutes)}분 전"

datetime(2026, 6, 19, 14, 30) 은 "2026년 6월 19일 14시 30분" 이라는 시각 값이에요. strftime 은 이 값을 우리가 보기 좋은 글자로 바꿔줘요. %Y 는 연도, %m 은 월, %d 는 일, %H:%M 은 시:분이에요.

Python
posted = datetime(2026, 6, 19, 14, 30)
print(format_posted_at(posted))  # 2026-06-19 14:30

now = datetime(2026, 6, 19, 17, 45)
print(time_ago(posted, now))  # 3시간 전
텍스트
2026-06-19 14:30
3시간 전

now - posted 처럼 시각끼리 빼면 차이(timedelta)가 나와요. 그 차이를 초로 바꿔(total_seconds) 3600 으로 나누면 몇 시간인지 나오죠. 그렇게 "3시간 전" 을 만들었어요. (지금 시각은 datetime.now() 로 얻지만, 예제는 결과가 매번 바뀌지 않도록 시각을 고정해 뒀어요.)

timedelta 로 기간을 더하거나 빼면 날짜 계산도 쉬워요.

Python
print(format_posted_at(posted - timedelta(days=1)))   # 2026-06-18 14:30
print(format_posted_at(posted + timedelta(weeks=1)))  # 2026-06-26 14:30
텍스트
2026-06-18 14:30
2026-06-26 14:30

하루 전, 일주일 뒤가 월·일 경계를 알아서 넘기며 정확히 계산돼요.

이제 "시간대(timezone)" 를 짚을 차례예요. 같은 순간이라도 한국은 오후 2시, 영국은 오전 5시예요. 서비스가 전 세계 사용자를 상대하면, 시각을 어느 나라 기준으로 저장할지가 문제가 돼요. 실무의 정석은 이래요. 저장은 세계 표준시(UTC) 로 통일하고, 보여줄 때만 사용자의 시간대로 바꾼다. 그래야 시차로 꼬이지 않아요.

Python
def to_seoul(utc_dt: datetime) -> datetime:
    """UTC로 저장된 시각을 한국(서울) 시간으로 변환한다."""
    return utc_dt.astimezone(ZoneInfo("Asia/Seoul"))
Python
utc_time = datetime(2026, 6, 19, 5, 30, tzinfo=timezone.utc)
seoul_time = to_seoul(utc_time)
print(format_posted_at(utc_time))   # 2026-06-19 05:30
print(format_posted_at(seoul_time))  # 2026-06-19 14:30  (UTC+9)
텍스트
2026-06-19 05:30
2026-06-19 14:30

tzinfo=timezone.utc 를 붙이면 "이건 UTC 시각" 이라고 꼬리표가 달려요. astimezone(ZoneInfo("Asia/Seoul")) 은 그 시각을 서울 시간으로 바꿔주고요. UTC 05:30 이 서울에선 14:30, 딱 9시간 차이로 정확히 변환됐어요.

⚠️ 지금 시각을 UTC 로 얻을 땐 datetime.now(timezone.utc) 를 써요. 예전 자료엔 datetime.utcnow() 가 보이는데, 이건 시간대 꼬리표가 없어 헷갈림을 일으켜서 파이썬 3.14 에선 아예 사라졌어요. 새로 짜는 코드는 datetime.now(timezone.utc) 로만 쓰면 돼요. 그리고 ZoneInfo("Asia/Seoul") 같은 시간대 이름은 윈도우에선 자료가 기본 제공되지 않을 수 있어, 한 번 uv add tzdata 로 시간대 데이터를 받아 두면 어느 컴퓨터에서나 똑같이 동작해요.

💡 한 줄 정리

datetime 으로 시각을 다루고 timedelta 로 기간을 더하고 빼요. 시간대가 얽히면 저장은 UTC(datetime.now(timezone.utc))로, 표시는 astimezone(ZoneInfo(...)) 로 바꿔요.

🙋 학생 질문 — "튜터님, 그냥 한국 시간으로 저장하면 편한데 왜 UTC 로 저장해요?"

지금 한국 사용자만 있으면 그래도 큰 문제는 없어요. 하지만 두 가지가 발목을 잡아요.

첫째, 사용자가 여러 나라에 있으면 "이 시각이 누구 기준이지?" 가 헷갈려요. 저장된 값이 한국 시간인지 미국 시간인지 알 수 없으면 계산이 엉켜요. 둘째, 어떤 나라는 여름·겨울에 시계를 한 시간씩 옮기는 서머타임이 있어서, 그 나라 시간으로 저장하면 같은 "9시" 가 계절마다 다른 순간을 가리킬 수 있어요. UTC 는 전 세계 공통이고 서머타임도 없어서, 저장 기준으로 삼으면 이런 혼란이 사라져요. "저장은 UTC, 표시는 현지 시간" 은 그래서 거의 모든 서비스가 따르는 약속이에요.


Step 6: "글에서 패턴 찾기 — 정규식(re)"

캡션에서 해시태그(#일상)나 멘션(@minji)만 뽑아내려면 어떻게 할까요? 지난 A-2 에서 split 으로 공백을 잘라 봤지만, "# 으로 시작하는 단어만" 같은 조건은 split 으로는 까다로워요.

이런 "패턴으로 글자를 찾는" 일에 쓰는 게 정규식(regular expression)이에요. 정규식은 "어떤 모양의 글자를 찾을지" 를 적는 작은 패턴 언어예요. 파이썬에선 re 라는 표준 라이브러리로 써요. 자주 쓰는 기호 몇 개만 알면 시작할 수 있어요.

텍스트
 정규식 기본 기호 몇 가지

  \d   숫자 한 글자          (0~9)
  \w   글자 한 글자          (영문·숫자·밑줄, 한글도 포함)
  +    바로 앞 것이 1개 이상  (\w+ = 글자가 한 개 이상)
  *    바로 앞 것이 0개 이상
  ()   그룹 — 매치 중 일부만 따로 꺼내기

re.findall(패턴, 글) 은 글 안에서 패턴에 맞는 부분을 전부 찾아 리스트로 돌려줘요. 해시태그를 뽑아 볼게요. 패턴 #\w+ 는 "# 다음에 글자가 한 개 이상" 이라는 뜻이에요.

Python
# src/g_stdlib/g1_regex.py
import re


def extract_hashtags(caption: str) -> list[str]:
    """캡션에서 #해시태그를 모두 뽑는다 (# 뒤 글자·숫자·밑줄)."""
    return re.findall(r"#\w+", caption)


def extract_mentions(caption: str) -> list[str]:
    """캡션에서 @멘션의 이름만 모두 뽑는다 (괄호 그룹으로 @ 제외)."""
    return re.findall(r"@(\w+)", caption)

패턴 앞에 붙은 r 은 "raw 문자열" 이라는 표시예요. 정규식엔 \ 가 자주 나오는데, r"..." 로 감싸면 \ 를 그대로 패턴에 넘길 수 있어 정규식엔 거의 항상 붙여 줘요.

Python
caption = "오늘 @minji 랑 카페 #일상 #카페_투어 좋아요 25개"

print(extract_hashtags(caption))  # ['#일상', '#카페_투어']
print(extract_mentions(caption))  # ['minji']
텍스트
['#일상', '#카페_투어']
['minji']

#\w+#일상#카페_투어 를 정확히 찾아냈어요. \w 는 한글도 글자로 쳐주고, 밑줄(_)도 포함해서 #카페_투어 가 통째로 잡혔죠. 멘션 패턴 @(\w+) 엔 괄호가 있는데, 이 괄호(그룹)는 "매치된 것 중에 이 부분만 따로 꺼내라" 는 뜻이에요. 그래서 @ 는 빼고 이름 minji 만 나왔어요.

하나만 찾을 땐 re.search 를 써요. 처음 맞는 부분 하나를 찾아주고, 없으면 None 을 돌려줘요.

Python
def find_first_number(text: str) -> str | None:
    """글에서 처음 나오는 숫자 덩어리를 찾는다. 없으면 None."""
    match = re.search(r"\d+", text)
    return match.group() if match else None
Python
print(find_first_number(caption))  # 25
print(find_first_number("숫자 없음"))  # None
텍스트
25
None

\d+ 는 "숫자가 한 개 이상" 이라, 위 캡션에서 처음 나오는 숫자 25 를 찾았어요. 숫자가 없으면 None 이 나오죠. 지난 F-1 에서 본 str | None(글자 또는 None)이 여기서 자연스럽게 쓰였어요.

💡 한 줄 정리

정규식(re)은 "어떤 모양의 글자" 를 찾는 패턴 언어예요. re.findall 로 맞는 걸 다 찾고, re.search 로 처음 하나만 찾아요. \d(숫자)·\w(글자)·+(1개 이상)·()(그룹)만 알아도 시작할 수 있어요.

🙋 학생 질문 — "튜터님, 정규식 기호를 다 외워야 하나요? 너무 많아 보여요."

다 외울 필요 전혀 없어요. 저도 자주 쓰는 몇 개(\d·\w·+·())만 머리에 두고, 복잡한 패턴은 그때그때 찾아 써요.

정규식은 강력한 만큼 길어지면 읽기 어려워져요. 그래서 실무에서도 "간단한 추출은 정규식, 그보다 복잡하면 차라리 코드로 풀기" 를 저울질해요. 오늘은 "#태그·@멘션·숫자 뽑기" 정도의 기초만 익히면 충분해요. 이 정도만 알아도 글에서 원하는 걸 꽤 많이 뽑아낼 수 있어요.


Step 7: "무작위로 뽑고 섞기 — random"

지난 C-3 에서 random.choice 로 추천 해시태그를 하나 뽑고, random.seed 로 결과를 고정해 봤죠. 오늘은 거기에 몇 가지를 더 얹어요. 정수 뽑기(randint), 순서 섞기(shuffle), 여럿 뽑기(sample) 예요.

Python
# src/g_stdlib/g1_random.py
import random


def pick_recommended_tag(tags: list[str]) -> str:
    """추천 해시태그 하나를 무작위로 뽑는다."""
    return random.choice(tags)


def shuffled_feed(posts: list[str]) -> list[str]:
    """피드 순서를 무작위로 섞는다 (원본은 두고 새 리스트로)."""
    copied = posts[:]
    random.shuffle(copied)
    return copied


def pick_sample(members: list[str], k: int) -> list[str]:
    """회원 중 중복 없이 k명을 뽑는다."""
    return random.sample(members, k)

shuffled_feed 를 보면 posts[:] 로 먼저 복사를 떠요. random.shuffle 은 받은 리스트를 그 자리에서 직접 섞기 때문에, 원본을 지키려고 복사본을 섞는 거예요. 이건 지난 B-1 에서 본 "별칭과 복사" 의 연장이에요.

Python
random.seed(7)  # 씨앗 고정 → 실행할 때마다 같은 결과 (C-3 에서 본 seed 기법)

tags = ["#일상", "#여행", "#카페", "#운동"]
print(pick_recommended_tag(tags))
print(random.randint(1, 100))  # 1~100 사이 정수 하나
print(shuffled_feed(["1번", "2번", "3번", "4번", "5번"]))
print(pick_sample(["민지", "재훈", "승우", "지아"], 2))
텍스트
#카페
20
['2번', '3번', '5번', '1번', '4번']
['승우', '지아']

choice 는 하나를 콕 집어 뽑고, randint(1, 100) 은 1부터 100 사이 정수를 줘요. shuffle 은 다섯 개의 순서를 뒤섞었고, sample(..., 2) 은 네 명 중 겹치지 않게 두 명을 뽑았어요.

맨 앞에 random.seed(7) 이 있어서 이 결과는 실행할 때마다 똑같아요. 씨앗을 고정하면 "무작위인데 재현 가능" 해져서, 예제나 테스트에서 결과를 맞춰 보기 좋아요. 씨앗을 빼면 매번 다른 결과가 나오고요.

💡 한 줄 정리

randomchoice(하나 뽑기)·randint(정수)·shuffle(순서 섞기)·sample(겹침 없이 여럿 뽑기)을 줘요. seed 를 고정하면 같은 결과를 재현할 수 있어요.

🙋 학생 질문 — "튜터님, shuffle 은 왜 결과를 return 하지 않아요?"

random.shuffle 은 받은 리스트를 그 자리에서(in place) 직접 섞어요. 새 리스트를 만들어 돌려주는 게 아니라, 넘긴 리스트 자체의 순서를 바꿔 버리는 거죠. 그래서 result = random.shuffle(x) 처럼 받으면 result 엔 아무것도(None) 안 담겨요.

원본을 지키고 싶을 땐 예제처럼 posts[:] 로 복사본을 떠서 그걸 섞으면 돼요. 반대로 "이미 뒤섞인 새 리스트가 필요하다" 면 random.sample(x, len(x)) 를 쓰는 방법도 있어요. 이건 원본을 건드리지 않고 섞인 새 리스트를 돌려줘요.


Step 8: "도구 총동원 — 피드 인사이트"

마지막으로 오늘 배운 도구들을 한데 모아 작은 분석 기능을 만들어 볼게요. 피드 전체 캡션에서 "이번 주 인기 해시태그 순위" 를 뽑고, 분석한 시각까지 붙여 한 줄 리포트로 내는 거예요.

흐름은 이래요. 정규식(re)으로 각 캡션에서 해시태그를 뽑고 → Counter 로 빈도를 세고 → most_common 으로 순위를 매기고 → datetime 으로 시각을 붙여요.

Python
# src/g_stdlib/g1_insights.py
import re
from collections import Counter
from datetime import datetime


def top_hashtags_in_feed(captions: list[str], n: int) -> list[tuple[str, int]]:
    """피드 전체 캡션에서 해시태그를 뽑아 가장 많이 쓰인 n개를 돌려준다."""
    all_tags = []
    for caption in captions:
        all_tags.extend(re.findall(r"#\w+", caption))
    return Counter(all_tags).most_common(n)


def feed_report(captions: list[str], analyzed_at: datetime) -> str:
    """인기 해시태그 3개와 분석 시각을 한 줄 리포트로 만든다."""
    top = top_hashtags_in_feed(captions, 3)
    tags_text = ", ".join(f"{tag}({count})" for tag, count in top)
    when = analyzed_at.strftime("%Y-%m-%d %H:%M")
    return f"[{when}] 인기 해시태그: {tags_text}"

top_hashtags_in_feed 는 캡션마다 re.findall 로 해시태그를 뽑아 all_tags 에 모아요(extend 는 리스트를 통째로 이어 붙여요). 그리고 그 전체를 Counter 에 넣어 most_common 으로 순위를 내죠. feed_report 는 그 순위를 #태그(횟수) 모양으로 엮고, 분석 시각을 앞에 붙여요.

Python
feed = [
    "카페 가는 길 #일상 #카페",
    "주말 여행 #여행 #일상",
    "홈카페 #카페 #일상",
    "운동 시작 #운동 #일상",
]

print(top_hashtags_in_feed(feed, 2))

report = feed_report(feed, datetime(2026, 6, 19, 17, 0))
print(report)
텍스트
[('#일상', 4), ('#카페', 2)]
[2026-06-19 17:00] 인기 해시태그: #일상(4), #카페(2), #여행(1)

게시물 네 개에 흩어져 있던 해시태그를 모아 보니 #일상 이 4번으로 가장 많고, #카페 가 2번이에요. 한 줄 리포트엔 분석 시각과 상위 3개가 깔끔하게 담겼고요. 오늘 배운 도구 네 가지(re·Counter·most_common·datetime)가 한 함수 안에서 자연스럽게 이어졌어요. 이게 표준 라이브러리의 힘이에요. 각자는 작은 도구지만, 엮으면 꽤 그럴듯한 기능이 돼요.

💡 한 줄 정리

표준 라이브러리는 따로 쓸 때도 편하지만, reCounterdatetime 처럼 엮으면 "피드 분석" 같은 실제 기능이 빠르게 만들어져요.

🙋 학생 질문 — "튜터님, 오늘 배운 도구를 다 외워야 하나요?"

외우기보다 "이런 게 있다" 를 기억하는 게 훨씬 중요해요. 막상 빈도를 세야 할 때 "아, Counter 가 있었지" 하고 떠올려 찾아 쓰면 그걸로 충분해요.

오늘 다룬 건 표준 라이브러리의 아주 일부예요. 파이썬엔 이런 도구가 정말 많고, 전부 아는 사람은 없어요. 중요한 건 "직접 만들기 전에, 이미 좋은 도구가 있는지 먼저 찾아보는 습관" 이에요. 그 습관만 들면, 필요한 도구는 그때그때 문서를 보며 꺼내 쓰면 돼요.


마무리

오늘은 파이썬에 딸려 오는 도구상자를 열어 자주 쓰는 표준 라이브러리를 골라 익혔어요. 빈도를 세는 Counter, 기본값을 주는 defaultdict, 양끝이 빠른 deque 부터, 반복을 다루는 itertools, 시간을 다루는 datetime, 패턴을 찾는 re, 무작위의 random 까지요. 마지막엔 이걸 엮어 피드 인사이트까지 만들었죠.

오늘 배운 핵심 세 가지

💡 하나: collectionsCounter(빈도)·defaultdict(기본값)·deque(양끝 큐)는 자주 만나는 작업을 짧고 안전하게 해줘요.

💡 : itertools(엮기·자르기·조합)·datetime(시각·시간대)·re(패턴)·random(무작위)은 각 분야의 단골 도구예요. 시간대는 "저장은 UTC, 표시는 현지" 가 약속이에요.

💡 : 표준 라이브러리는 설치 없이 import 만으로 쓰고, 엮으면 더 큰 기능이 돼요. "직접 만들기 전에 이미 있는 도구를 찾는 습관" 이 핵심이에요.

다음 시간 예고

여기까지 우리는 변수부터 비동기, 그리고 표준 라이브러리까지 파이썬을 한 바퀴 돌았어요. 그런데 지금까지 만든 코드가 "진짜 잘 도는지" 는 매번 직접 실행해 눈으로 확인했죠. 다음 시간엔 그 확인을 자동으로 해주는 도구, 테스트(pytest) 를 만나요. 함수 하나를 두고 "이 입력엔 이 결과가 나와야 한다" 를 적어 두면, 코드를 고칠 때마다 컴퓨터가 알아서 검사해 줘요. 오늘 만든 해시태그 빈도 같은 함수가 잘 도는지 자동으로 확인하면서, 이 과정을 멋지게 닫을 거예요.


과제

오늘 배운 표준 라이브러리를 직접 손으로 꺼내 써볼 차례예요. 막히면 해당 Step 을 다시 펼쳐 보세요. 각 함수는 uv run python 으로 실행해 확인하면 돼요.

[기초] 인기 해시태그 순위 매기기

해시태그가 잔뜩 담긴 리스트를 받아, 가장 많이 나온 순서대로 순위를 출력하는 tag_ranking 함수를 만들어 보세요.

Python
from collections import Counter


def tag_ranking(tags: list[str], n: int = 3):
    # 여기를 채워 보세요 — 가장 많이 나온 n개를 (태그, 횟수) 로 돌려주세요
    ...


tags = ["#봄", "#꽃", "#봄", "#여행", "#봄", "#꽃"]
# 1위: #봄 (3회) / 2위: #꽃 (2회) / 3위: #여행 (1회) 처럼 출력
  • Countermost_common(n) 을 떠올리세요. 한 줄이면 순위가 나와요.
  • 출력할 땐 지난 B-3 에서 본 enumerate(..., start=1) 로 "1위·2위·3위" 번호를 붙여 보세요.

[응용] 가장 많이 멘션된 회원 찾기

여러 캡션을 받아, @멘션 을 모두 뽑은 뒤 가장 많이 언급된 회원을 찾는 most_mentioned 함수를 만들어 보세요.

Python
import re
from collections import Counter


def most_mentioned(captions: list[str], n: int = 3):
    # 여기를 채워 보세요 — @멘션을 모아 가장 많이 나온 n명을 돌려주세요
    ...


captions = [
    "@minji 사진 잘 나왔다",
    "@minji @jaehoon 같이 카페",
    "@jaehoon 멋져요",
    "@minji 또 보자",
]
# [('minji', 3), ('jaehoon', 2)] 처럼 나와야 해요
  • Step 6 의 re.findall(r"@(\w+)", ...) 로 이름만 뽑고, Step 1 의 Counter 로 세면 돼요.
  • 캡션이 여러 개니, 멘션을 한 리스트에 모은 뒤(extend) 한꺼번에 세는 게 깔끔해요.

[심화] 게시 시각을 한국 시간으로 보여주기

UTC 로 저장된 게시 시각을 받아, 한국(서울) 시간 문자열로 바꾸는 to_seoul 과, 현재 시각과의 차이를 "N시간 전" 으로 알려주는 hours_ago 를 만들어 보세요.

Python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo


def to_seoul(utc_dt: datetime) -> str:
    # 여기를 채워 보세요 — 서울 시간 'YYYY-MM-DD HH:MM' 문자열로
    ...


def hours_ago(posted: datetime, now: datetime) -> str:
    # 여기를 채워 보세요 — 'N시간 전' 으로
    ...


posted_utc = datetime(2026, 6, 19, 0, 0, tzinfo=timezone.utc)
# to_seoul(posted_utc) → '2026-06-19 09:00' (UTC+9)
  • astimezone(ZoneInfo("Asia/Seoul")) 로 시간대를 바꾸고 strftime 으로 글자로 만드세요.
  • 차이는 Step 5 처럼 now - postedtotal_seconds() 를 3600 으로 나누면 시간이 나와요.
  • 윈도우라면 uv add tzdata 를 한 번 해두면 시간대 이름이 잘 동작해요.

생각해볼 주제

1. 직접 만들기 vs 표준 도구 꺼내 쓰기

오늘 배운 Counterdefaultdict 는 사실 일반 딕셔너리로도 똑같이 만들 수 있어요. 그런데도 표준 도구를 쓰는 게 나은 이유는 뭘까요? 반대로, 아주 단순한 경우엔 그냥 딕셔너리가 더 나을 때도 있을까요? "코드를 읽는 사람에게 의도가 잘 드러나는가" 라는 기준으로 두 선택을 견줘 보세요.

2. 왜 시간은 UTC 로 저장할까

Step 5 에서 "저장은 UTC, 표시는 현지 시간" 이라는 약속을 봤어요. 만약 어떤 서비스가 모든 시각을 그냥 한국 시간으로 저장한다면, 사용자가 여러 나라로 늘어났을 때 어떤 문제가 생길지 상상해 보세요. 서머타임(여름에 시계를 옮기는 제도)이 있는 나라까지 생각하면 왜 UTC 가 안전한 기준인지 더 또렷해질 거예요.

3. 정규식은 강력한데, 언제 멈춰야 할까

정규식은 글에서 패턴을 뽑는 데 아주 강력해요. 하지만 패턴이 복잡해질수록 읽기가 어려워지죠. "#태그 를 뽑는다" 정도는 정규식이 깔끔하지만, 어떤 작업은 차라리 평범한 코드로 푸는 게 나을 수도 있어요. 정규식이 빛나는 경우와, 오히려 코드를 어렵게 만드는 경우를 각각 떠올려 보세요.

✅ 예시 답안정답 보기

과제와 생각해볼 주제의 예시답안이에요. 먼저 스스로 손으로 짠 뒤에 펼쳐 보세요. 표준 라이브러리는 "직접 만들어 보고 → 도구로 짧아지는" 흐름을 한 번 겪으면 훨씬 오래 남아요.


🎯 [과제 1 예시답안] 인기 해시태그 순위 매기기

채점 포인트

항목 배점 확인
Counter 로 빈도를 세는가 ★★★
most_common(n) 으로 상위 n개를 뽑는가 ★★★
enumerate(..., start=1) 로 순위 번호를 붙이는가 ★★☆

풀이 예시

Python
from collections import Counter


def tag_ranking(tags: list[str], n: int = 3) -> list[tuple[str, int]]:
    """해시태그 리스트에서 가장 많이 쓰인 n개를 (태그, 횟수)로 돌려준다."""
    return Counter(tags).most_common(n)
Python
tags = ["#봄", "#꽃", "#봄", "#여행", "#봄", "#꽃"]
for rank, (tag, count) in enumerate(tag_ranking(tags), start=1):
    print(f"{rank}위: {tag} ({count}회)")
텍스트
1위: #봄 (3회)
2위: #꽃 (2회)
3위: #여행 (1회)

핵심은 Counter(tags).most_common(n) 한 줄이에요. 빈도를 세는 일도, 많은 순으로 정렬하는 일도 Counter 가 한 번에 해줘요. 출력은 지난 B-3 의 enumerate 로 "1위·2위·3위" 번호를 붙였어요.

most_common(n) 은 이미 많은 순으로 정렬돼 나와요 — 따로 sorted 할 필요가 없어요. ❌ 일반 딕셔너리로 직접 세고 정렬하면 — 결과는 같지만 코드가 길어지고 실수할 틈이 생겨요.

💡 튜터의 한마디: "빈도 세고 → 많은 순으로" 는 정말 자주 나오는 작업이에요. 그때마다 Countermost_common 을 떠올리면, 직접 세고 정렬하는 수고를 거의 다 덜 수 있어요.

🎯 [과제 2 예시답안] 가장 많이 멘션된 회원 찾기

채점 포인트

항목 배점 확인
re.findall(r"@(\w+)", ...) 로 이름만 뽑는가 ★★★
여러 캡션의 멘션을 한 리스트에 모으는가(extend) ★★☆
Counter.most_common(n) 으로 많이 멘션된 회원을 찾는가 ★★★

풀이 예시

Python
import re
from collections import Counter


def most_mentioned(captions: list[str], n: int = 3) -> list[tuple[str, int]]:
    """여러 캡션에서 @멘션을 모아 가장 많이 언급된 회원 n명을 돌려준다."""
    mentions = []
    for caption in captions:
        mentions.extend(re.findall(r"@(\w+)", caption))
    return Counter(mentions).most_common(n)
Python
captions = [
    "@minji 사진 잘 나왔다",
    "@minji @jaehoon 같이 카페",
    "@jaehoon 멋져요",
    "@minji 또 보자",
]
print(most_mentioned(captions, 2))
텍스트
[('minji', 3), ('jaehoon', 2)]

Step 6 의 그룹 패턴 @(\w+) 으로 @ 는 빼고 이름만 뽑고, Step 1 의 Counter 로 셌어요. 캡션이 여러 개라 extend 로 멘션을 한 리스트에 모은 뒤 한꺼번에 세는 게 깔끔해요. minji 가 3번으로 가장 많이 언급됐네요.

findall 결과(리스트)를 extend 로 이어 붙였는가 — append 면 리스트 안에 리스트가 들어가 버려요. ❌ re.findall(r"@\w+", ...) 처럼 괄호를 빼면 — @minji 처럼 @ 까지 붙어 나와요. 이름만 원하면 그룹 () 가 필요해요.

💡 튜터의 한마디: re 로 뽑고 Counter 로 세는 조합은 "글에서 무언가를 뽑아 순위 내기" 의 단골 짝이에요. 해시태그·멘션·키워드 무엇이든 같은 패턴으로 풀려요.

🎯 [과제 3 예시답안] 게시 시각을 한국 시간으로 보여주기

채점 포인트

항목 배점 확인
astimezone(ZoneInfo("Asia/Seoul")) 로 시간대를 바꾸는가 ★★★
strftime 으로 보기 좋은 문자열로 만드는가 ★★☆
total_seconds() 차이로 "N시간 전" 을 구하는가 ★★★

풀이 예시

Python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo


def to_seoul(utc_dt: datetime) -> str:
    """UTC 시각을 한국(서울) 시간 문자열 'YYYY-MM-DD HH:MM' 으로 바꾼다."""
    seoul = utc_dt.astimezone(ZoneInfo("Asia/Seoul"))
    return seoul.strftime("%Y-%m-%d %H:%M")


def hours_ago(posted: datetime, now: datetime) -> str:
    """게시 시각과 현재 시각의 차이를 'N시간 전' 으로."""
    diff = now - posted
    hours = int(diff.total_seconds() // 3600)
    return f"{hours}시간 전"
Python
posted_utc = datetime(2026, 6, 19, 0, 0, tzinfo=timezone.utc)
print(to_seoul(posted_utc))  # 2026-06-19 09:00

now_utc = datetime(2026, 6, 19, 5, 0, tzinfo=timezone.utc)
print(hours_ago(posted_utc, now_utc))  # 5시간 전
텍스트
2026-06-19 09:00
5시간 전

UTC 00:00 이 서울에선 09:00 으로, 딱 9시간 차이로 변환됐어요. "N시간 전" 은 두 시각을 빼서 나온 차이(total_seconds)를 3600 으로 나눈 거고요. 저장은 UTC 로 통일하고, 보여줄 때만 서울 시간으로 바꾸는 흐름 그대로예요.

✅ 저장된 값(posted_utc)에 tzinfo=timezone.utc 꼬리표가 달려 있는가 — 그래야 astimezone 이 정확히 변환해요. ❌ datetime.utcnow() 로 현재 UTC 를 구하려 하면 — 파이썬 3.14 에선 사라진 방법이라 실행되지 않아요. datetime.now(timezone.utc) 를 써요.

💡 튜터의 한마디: 윈도우에서 ZoneInfo("Asia/Seoul") 가 안 먹히면 uv add tzdata 한 번이면 해결돼요. 시간대 데이터를 챙겨 두면 어느 컴퓨터에서나 똑같이 동작해요.


생각해볼 주제 예시답안

🤔 [생각해볼 주제 1] 직접 만들기 vs 표준 도구 꺼내 쓰기

문제 상황 요약

Counterdefaultdict 는 일반 딕셔너리로도 똑같이 만들 수 있어요. 그런데도 표준 도구를 쓰는 게 나은 이유는 뭘까요? 반대로 그냥 딕셔너리가 더 나을 때도 있을까요?

튜터의 가이드 및 해설

핵심 기준은 "코드를 읽는 사람에게 의도가 얼마나 빨리 드러나는가" 예요.

Counter(tags) 한 줄을 보면, 읽는 사람은 곧장 "아, 빈도를 세는구나" 를 알아요. 같은 일을 일반 딕셔너리로 "키가 있나 확인하고 +1 하고…" 로 풀면, 결과는 같아도 읽는 사람이 코드를 한참 따라가야 의도를 파악해요. 표준 도구는 "무엇을 하려는지" 를 이름으로 먼저 알려주는 셈이에요. 게다가 이미 검증된 코드라, 직접 짜다 흔히 내는 실수(없는 키 처리 빠뜨리기 같은)도 줄여줘요.

반대로 표준 도구가 과한 경우도 있어요. 키가 두세 개뿐인 아주 단순한 딕셔너리에 굳이 defaultdict 를 끌어오면, 오히려 "왜 이걸 썼지?" 하고 한 번 더 생각하게 만들어요. 도구는 "그 도구가 풀려는 문제가 실제로 있을 때" 빛나요. 빈도·그룹·큐처럼 그 도구의 이름값을 하는 상황이면 꺼내 쓰고, 그렇지 않으면 평범한 딕셔너리가 더 솔직해요.

💡 핵심을 한마디로

"표준 도구는 '무엇을 하려는지' 를 이름으로 먼저 보여줘요. 빈도엔 Counter, 그룹엔 defaultdict 처럼 도구가 풀려는 문제가 실제로 있을 때 쓰면, 코드가 짧아지고 의도가 또렷해져요."

🤔 [생각해볼 주제 2] 왜 시간은 UTC 로 저장할까

문제 상황 요약

어떤 서비스가 모든 시각을 그냥 한국 시간으로 저장한다면, 사용자가 여러 나라로 늘어났을 때 어떤 문제가 생길까요? 서머타임까지 생각하면 왜 UTC 가 안전한 기준일까요?

튜터의 가이드 및 해설

핵심은 "저장된 값 하나만 보고도 그게 정확히 어느 순간인지 알 수 있어야 한다" 는 거예요.

모든 시각을 한국 시간으로 저장하면, 미국 사용자가 올린 글의 시각도 한국 시간으로 적혀요. 나중에 "이 사용자에게 현지 시간으로 보여주자" 할 때, 저장된 값이 이미 한국 기준이라 다시 거꾸로 계산해야 하고, 그 과정에서 시차가 꼬이기 쉬워요. 기준이 흔들리면 계산이 전부 흔들려요.

서머타임은 함정을 하나 더 보태요. 어떤 나라는 여름에 시계를 한 시간 당겼다가 가을에 되돌려요. 그 나라 시간으로 저장하면, 같은 "오전 9시" 가 계절에 따라 다른 순간을 가리킬 수 있어요. 심지어 시계를 되돌리는 날엔 "오전 1시 30분" 이 하루에 두 번 오기도 해요. UTC 는 전 세계 공통이고 서머타임이 없어서 이런 애매함이 아예 없어요. 그래서 "저장은 흔들리지 않는 UTC 로, 보여줄 때만 사용자의 현지 시간으로" 가 거의 모든 서비스의 약속이 됐어요.

💡 핵심을 한마디로

"UTC 는 전 세계 공통이고 서머타임도 없어서, 저장 기준으로 삼으면 시각이 절대 흔들리지 않아요. 저장은 UTC, 표시는 현지 시간 — 이 한 줄이 시차 버그를 거의 다 막아줘요."

🤔 [생각해볼 주제 3] 정규식은 강력한데, 언제 멈춰야 할까

문제 상황 요약

정규식은 패턴을 뽑는 데 강력하지만, 복잡해질수록 읽기 어려워져요. 정규식이 빛나는 경우와, 오히려 코드를 더 어렵게 만드는 경우는 각각 언제일까요?

튜터의 가이드 및 해설

기준은 "패턴이 단순하고 또렷한가, 아니면 예외가 많고 길어지는가" 예요.

정규식이 빛나는 건 "모양이 일정한" 것을 뽑을 때예요. #태그@멘션 처럼 "특정 기호로 시작하는 단어" 는 #\w+ 한 줄로 깔끔하게 잡혀요. 이런 건 일반 코드로 풀면 오히려 더 길어져요. 전화번호·이메일처럼 모양이 정해진 것도 정규식이 잘 맞아요.

반대로, 규칙이 많고 예외가 얽히면 정규식은 금세 외계어처럼 길어져요. 기호가 빽빽한 한 줄이 되면, 한 달 뒤의 나조차 그게 뭘 하는지 못 읽어요. 이럴 땐 차라리 평범한 코드로 단계를 나눠 푸는 게 읽기 쉽고 고치기도 쉬워요. "이 정규식을 다른 사람이(또는 미래의 내가) 한눈에 읽을 수 있을까?" 를 물어보고, 답이 망설여지면 코드로 풀어 쓰는 게 좋은 신호예요. 정규식은 강력한 도구지만, 강력하다고 늘 답은 아니에요.

💡 핵심을 한마디로

"모양이 일정한 걸 뽑을 땐 정규식 한 줄이 가장 깔끔해요. 하지만 예외가 얽혀 기호가 빽빽해지면, '나중에 이걸 읽을 수 있을까?' 를 묻고 평범한 코드로 풀어 쓰는 게 나아요."

전체 목록 파이썬 기초