표준 라이브러리 핵심 — os, sys, re, datetime, collections, itertools
Python의 **표준 라이브러리(stdlib)**는 추가 설치 없이 바로 사용할 수 있는 강력한 모듈 집합입니다. 실무에서 가장 많이 사용하는 핵심 모듈을 완전 정복합니다.
os — 운영체제 인터페이스
파일 시스템, 환경 변수, 프로세스 관리에 사용합니다.
import os
# 환경 변수
db_url = os.environ.get("DATABASE_URL", "sqlite:///dev.db")
os.environ["MY_VAR"] = "value"
# 현재 디렉터리
print(os.getcwd())
os.chdir("/tmp")
# 경로 조작
path = os.path.join("data", "2024", "report.csv")
print(os.path.exists(path))
print(os.path.dirname(path)) # data/2024
print(os.path.basename(path)) # report.csv
print(os.path.splitext(path)) # ('data/2024/report', '.csv')
# 파일/디렉터리 생성 및 삭제
os.makedirs("logs/2024/03", exist_ok=True)
os.remove("old_file.txt")
os.rmdir("empty_dir")
# 디렉터리 내용 순회
for dirpath, dirnames, filenames in os.walk("project/"):
for fname in filenames:
full_path = os.path.join(dirpath, fname)
print(full_path)
# 프로세스 정보
print(os.getpid()) # 현재 프로세스 ID
print(os.cpu_count()) # CPU 코어 수
# 명령 실행 (subprocess 사용 권장)
exit_code = os.system("ls -la")
sys — 인터프리터 정보
Python 인터프리터 자체에 접근하는 모듈입니다.
import sys
# Python 버전
print(sys.version) # '3.12.0 (main, ...'
print(sys.version_info) # sys.version_info(major=3, minor=12, ...)
# 플랫폼
print(sys.platform) # 'linux', 'darwin', 'win32'
# 커맨드라인 인수
# python script.py arg1 arg2 --flag
print(sys.argv) # ['script.py', 'arg1', 'arg2', '--flag']
# 표준 스트림
sys.stdout.write("표준 출력\n")
sys.stderr.write("오류 출력\n")
# 모듈 검색 경로
sys.path.insert(0, "/custom/lib")
# 최대 재귀 깊이
print(sys.getrecursionlimit()) # 1000 (기본값)
sys.setrecursionlimit(5000)
# 인터프리터 종료
# sys.exit(0) # 정상 종료
# sys.exit(1) # 오류 종료
# 객체 크기 (바이트)
print(sys.getsizeof([1, 2, 3])) # 80
print(sys.getsizeof("hello")) # 54
# 버전 체크 패턴
if sys.version_info < (3, 12):
raise RuntimeError("Python 3.12 이상이 필요합니다.")
re — 정규표현식 완전 정복
import re
text = "2024년 3월 15일, 연락처: 010-1234-5678, 이메일: user@example.com"
# 기본 함수들
# re.search(): 첫 번째 매치 찾기
match = re.search(r"\d{4}년", text)
if match:
print(match.group()) # 2024년
print(match.start()) # 0
print(match.end()) # 6
# re.findall(): 모든 매치를 리스트로
phones = re.findall(r"\d{3}-\d{4}-\d{4}", text)
print(phones) # ['010-1234-5678']
# re.finditer(): 모든 매치를 이터레이터로
for m in re.finditer(r"\d+", text):
print(f"{m.group()} at {m.start()}")
# re.sub(): 치환
cleaned = re.sub(r"\d{3}-\d{4}-\d{4}", "***-****-****", text)
# re.split(): 분리
parts = re.split(r"[,\s]+", "apple, banana cherry")
print(parts) # ['apple', 'banana', 'cherry']
# 그룹 캡처
date_pattern = re.compile(r"(\d{4})년\s*(\d{1,2})월\s*(\d{1,2})일")
m = date_pattern.search(text)
if m:
year, month, day = m.groups()
print(f"{year}-{month:>02}-{day:>02}")
# 이름 있는 그룹
email_pattern = re.compile(
r"(?P<user>[\w.]+)@(?P<domain>[\w.]+)\.(?P<tld>[a-z]{2,})"
)
m = email_pattern.search(text)
if m:
print(m.group("user")) # user
print(m.group("domain")) # example
print(m.groupdict()) # {'user': 'user', 'domain': 'example', 'tld': 'com'}
# 컴파일된 패턴으로 성능 향상
EMAIL_RE = re.compile(
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
re.IGNORECASE
)
def extract_emails(text: str) -> list[str]:
return EMAIL_RE.findall(text)
# re.VERBOSE로 가독성 높은 패턴
url_pattern = re.compile(r"""
https?:// # 스킴
(?:www\.)? # 선택적 www
([\w-]+) # 도메인
\.([a-z]{2,}) # TLD
(/[\w./%-]*)? # 경로 (선택)
""", re.VERBOSE)
datetime — 날짜와 시간 처리
from datetime import date, time, datetime, timedelta, timezone
import datetime as dt
# date: 날짜만
today = date.today()
print(today) # 2024-03-15
print(today.year, today.month, today.day)
birthday = date(1990, 5, 20)
print((today - birthday).days) # 나이를 일수로
# time: 시간만
t = time(14, 30, 0)
print(t.isoformat()) # '14:30:00'
# datetime: 날짜 + 시간
now = datetime.now()
utc_now = datetime.now(timezone.utc)
print(now.isoformat()) # '2024-03-15T14:30:00.123456'
print(now.strftime("%Y년 %m월 %d일 %H:%M")) # '2024년 03월 15일 14:30'
# 문자열 파싱
dt_obj = datetime.strptime("2024-03-15 14:30", "%Y-%m-%d %H:%M")
# timedelta: 기간
delta = timedelta(days=30, hours=2, minutes=15)
future = now + delta
past = now - timedelta(weeks=2)
print(f"30일 후: {future.date()}")
# 타임존 처리 (Python 3.9+는 zoneinfo 사용 권장)
from zoneinfo import ZoneInfo
seoul = ZoneInfo("Asia/Seoul")
ny = ZoneInfo("America/New_York")
now_seoul = datetime.now(seoul)
now_ny = now_seoul.astimezone(ny)
print(f"서울: {now_seoul.strftime('%H:%M %Z')}")
print(f"뉴욕: {now_ny.strftime('%H:%M %Z')}")
# ISO 형식 파싱 (Python 3.7+)
dt_obj = datetime.fromisoformat("2024-03-15T14:30:00+09:00")
collections — 고급 컨테이너
Counter — 개수 세기
from collections import Counter
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
c = Counter(words)
print(c) # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(c.most_common(2)) # [('apple', 3), ('banana', 2)]
print(c["apple"]) # 3
# 문자 빈도
char_count = Counter("mississippi")
print(char_count) # Counter({'i': 4, 's': 4, 'p': 2, 'm': 1})
# Counter 연산
c1 = Counter(a=3, b=2)
c2 = Counter(a=1, b=4, c=1)
print(c1 + c2) # Counter({'b': 6, 'a': 4, 'c': 1})
print(c1 - c2) # Counter({'a': 2})
defaultdict — 기본값이 있는 dict
from collections import defaultdict
# 기본값 없는 dict는 KeyError 발생
# regular_dict = {}
# regular_dict["missing"] # KeyError!
# defaultdict는 기본값 자동 생성
word_positions = defaultdict(list)
text = "the quick brown fox jumps over the lazy dog"
for i, word in enumerate(text.split()):
word_positions[word].append(i)
print(word_positions["the"]) # [0, 6]
# 중첩 defaultdict
nested = defaultdict(lambda: defaultdict(int))
nested["group1"]["count"] += 1
OrderedDict
from collections import OrderedDict
# Python 3.7+에서 일반 dict도 순서 보장
# OrderedDict의 특수 기능: move_to_end
od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
od.move_to_end("a") # 맨 뒤로
od.move_to_end("c", last=False) # 맨 앞으로
print(list(od.keys())) # ['c', 'b', 'a']
# LRU 캐시 구현에 활용
deque — 양방향 큐
from collections import deque
d = deque([1, 2, 3], maxlen=5)
d.appendleft(0) # 왼쪽에 추가
d.append(4) # 오른쪽에 추가
d.popleft() # 왼쪽에서 제거 — O(1)!
d.pop() # 오른쪽에서 제거
# 슬라이딩 윈도우 패턴
def sliding_window_avg(data: list[float], size: int) -> list[float]:
window = deque(maxlen=size)
result = []
for val in data:
window.append(val)
if len(window) == size:
result.append(sum(window) / size)
return result
namedtuple — 이름 있는 튜플
from collections import namedtuple
from typing import NamedTuple
# 구식 방법
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p._asdict()) # {'x': 3, 'y': 4}
# 현대적 방법 (타입 힌트 포함)
class Coordinate(NamedTuple):
latitude: float
longitude: float
altitude: float = 0.0
coord = Coordinate(37.5665, 126.9780)
print(coord.latitude) # 37.5665
lat, lon, alt = coord # 언패킹
itertools — 이터레이터 도구
import itertools
# 무한 이터레이터
for i, val in enumerate(itertools.count(10, 2)):
print(val) # 10, 12, 14, ...
if i >= 4:
break
colors = itertools.cycle(["빨강", "초록", "파랑"])
for _ in range(6):
print(next(colors)) # 빨강, 초록, 파랑, 빨강, ...
# chain: 여러 이터러블 연결
result = list(itertools.chain([1, 2], [3, 4], [5, 6]))
print(result) # [1, 2, 3, 4, 5, 6]
# islice: 이터레이터 슬라이싱
first_5 = list(itertools.islice(itertools.count(), 5))
print(first_5) # [0, 1, 2, 3, 4]
# product: 데카르트 곱
for combo in itertools.product("AB", repeat=2):
print(combo) # ('A', 'A'), ('A', 'B'), ('B', 'A'), ('B', 'B')
# combinations, permutations
print(list(itertools.combinations([1, 2, 3], 2)))
# [(1, 2), (1, 3), (2, 3)]
print(list(itertools.permutations([1, 2, 3], 2)))
# [(1, 2), (1, 3), (2, 1), (2, 3), (3, 1), (3, 2)]
# groupby: 연속된 키로 그룹화
data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("A", 5)]
for key, group in itertools.groupby(data, key=lambda x: x[0]):
print(key, list(group))
# A [('A', 1), ('A', 2)]
# B [('B', 3), ('B', 4)]
# A [('A', 5)]
실전 예제: 로그 분석기
import re
import sys
from collections import Counter, defaultdict
from datetime import datetime
from pathlib import Path
from itertools import groupby
LOG_PATTERN = re.compile(
r"(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}) "
r"(?P<level>INFO|WARNING|ERROR|DEBUG) "
r"(?P<message>.+)"
)
def parse_log_line(line: str) -> dict | None:
m = LOG_PATTERN.match(line)
if not m:
return None
return {
"timestamp": datetime.strptime(m.group("timestamp"), "%Y-%m-%d %H:%M:%S"),
"level": m.group("level"),
"message": m.group("message"),
}
def analyze_logs(log_file: str) -> None:
path = Path(log_file)
if not path.exists():
print(f"파일 없음: {log_file}", file=sys.stderr)
sys.exit(1)
entries = []
with open(path, encoding="utf-8") as f:
for line in f:
entry = parse_log_line(line.strip())
if entry:
entries.append(entry)
# 레벨별 카운트
level_counts = Counter(e["level"] for e in entries)
print("=== 레벨별 로그 수 ===")
for level, count in level_counts.most_common():
print(f" {level}: {count}")
# 시간별 오류 집계
error_entries = [e for e in entries if e["level"] == "ERROR"]
hour_errors = defaultdict(int)
for entry in error_entries:
hour_errors[entry["timestamp"].hour] += 1
print("\n=== 시간별 오류 수 ===")
for hour in sorted(hour_errors):
print(f" {hour:02d}시: {hour_errors[hour]}")
if __name__ == "__main__":
analyze_logs(sys.argv[1] if len(sys.argv) > 1 else "app.log")
고수 팁
팁 1: os.scandir()은 os.listdir()보다 빠름
import os
# 느린 방법
files = [f for f in os.listdir(".") if os.path.isfile(f)]
# 빠른 방법 (stat 정보 캐싱)
files = [e.name for e in os.scandir(".") if e.is_file()]
팁 2: Counter 업데이트
from collections import Counter
total = Counter()
for batch in data_batches:
total.update(Counter(batch))
팁 3: itertools.chain.from_iterable로 중첩 리스트 평탄화
import itertools
nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
flat = list(itertools.chain.from_iterable(nested))
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
팁 4: datetime.timezone.utc vs pytz
from datetime import datetime, timezone
# Python 3.9+ — 추가 패키지 불필요
from zoneinfo import ZoneInfo
now_utc = datetime.now(timezone.utc)
now_kst = now_utc.astimezone(ZoneInfo("Asia/Seoul"))