Skip to content
This page has been auto-translated and may contain errors.View in English

모듈과 표준 라이브러리

Python은 무작위성, 수학, 날짜, 파일 경로 등 바로 사용할 수 있는 방대한 도구 모음을 함께 제공합니다. 이러한 도구들은 모듈에 들어 있으며, import로 코드에 가져옵니다. 이전 장에서 이미 import json을 사용해 보았습니다. 이 장에서는 import를 자세히 다루고 표준 라이브러리의 가장 유용한 부분을 소개합니다.

Python 표준 라이브러리는 일반적인 문제에 대한 검증되고 문서화된 해결책을 제공합니다. 모듈은 코드 구성의 단위입니다: 각 파일이 하나의 모듈이며, __init__.py가 있는 각 디렉터리가 하나의 패키지입니다. import 시스템은 모듈을 찾고, 필요한 경우 컴파일하며, sys.modules에 캐싱하여 한 번만 로드되도록 합니다.

import 시스템은 계층적 구조로 되어 있습니다: finder가 모듈을 찾고 loader가 컴파일하고 실행합니다. 결과는 sys.modules에 캐싱됩니다. import foofoo.py를 한 번 실행하고 모듈 객체를 현재 네임스페이스의 foo에 바인딩합니다. from foo import barbar만 바인딩합니다. sys.path, __init__.py, 상대 임포트에 대한 이해는 패키지를 만들 때 필수적입니다.

모듈 가져오기

가장 단순한 import는 모듈 전체를 가져와서 점 표기법으로 그 내용을 사용할 수 있게 해줍니다. 모듈에서 특정 이름만 가져와 접두사 없이 바로 사용할 수도 있습니다. 별칭(alias)은 긴 이름을 짧게 줄여줍니다.

import module은 현재 스코프에서 module이라는 이름에 모듈 객체를 바인딩합니다. from module import namename만 바인딩합니다. 별칭(import module as alias)은 서드파티 라이브러리에서 흔히 사용됩니다. from module import *는 피하세요: 네임스페이스를 오염시키고 어디서 온 이름인지 불분명하게 만듭니다.

import module은 전체 import 메커니즘을 트리거하고, 결과를 sys.modules에 캐싱하며, 모듈 객체를 바인딩합니다. from module import name은 문법적 설탕입니다: 여전히 전체 모듈을 임포트한 다음 name을 추출합니다. 순환 임포트는 흔한 함정입니다; 해결책은 보통 import를 함수 내부로 옮기거나 모듈 의존성을 재구성하는 것입니다. importlib.import_module()은 프로그래밍 방식의 import를 가능하게 해줍니다.

python
import math

math.sqrt(16)     # 4.0
math.pi           # 3.141592653589793
math.floor(3.9)   # 3
math.ceil(3.1)    # 4

모듈에서 특정 이름을 가져와서 바로 사용할 수 있습니다:

python
from math import sqrt, pi

sqrt(16)    # 4.0 ("math." 접두사 불필요)
pi          # 3.141592653589793

모듈이나 이름에 별칭을 붙여 짧게 만들 수 있습니다:

python
import math as m

m.sqrt(16)    # 4.0

from math import sqrt as square_root
square_root(25)    # 5.0

별칭은 인기 있는 서드파티 라이브러리에서 흔히 사용됩니다(import numpy as np, import pandas as pd). 표준 라이브러리 모듈에서는 전체 이름을 사용하는 것을 선호하세요; 코드를 더 읽기 쉽게 만듭니다.

random

random 모듈은 난수를 생성하고 무작위 선택을 합니다. 게임, 시뮬레이션, 무작위 샘플링 등 예측 불가능성이 필요한 모든 곳에 사용하세요. **시드(seed)**를 설정하면 결과를 재현할 수 있습니다: 동일한 시드는 매번 동일한 시퀀스를 생성합니다.

random은 Mersenne Twister 의사 난수 생성기를 사용합니다. 시드가 전체 시퀀스를 결정합니다; 같은 시드는 항상 같은 출력을 만듭니다. .choice()는 항목 하나를 선택하고, .choices()는 중복을 허용하며, .sample()은 중복 없이 선택합니다. .shuffle()은 리스트를 제자리에서 수정하고 None을 반환합니다.

random은 624-word 상태를 갖는 Mersenne Twister (MT19937) PRNG를 사용합니다. random.seed()는 상태를 초기화합니다; 호출하지 않으면 os.urandom()으로부터 시드가 생성됩니다. 암호학적 목적으로는 secrets를 대신 사용하세요: random은 암호학적으로 안전하지 않습니다. random.SystemRandom()은 동일한 API로 os.urandom()을 감싼 안전한 대안입니다.

python
import random

random.random()              # 0과 1 사이의 float (1 미포함)
random.randint(1, 10)        # 1부터 10까지의 정수 (양쪽 포함)
random.uniform(1.0, 10.0)    # 1.0과 10.0 사이의 float

colours = ["red", "green", "blue"]
random.choice(colours)       # 항목 하나 선택
random.choices(colours, k=3) # k개 항목 선택 (중복 허용)
random.sample(colours, k=2)  # k개 항목 선택 (중복 없음)

numbers = [1, 2, 3, 4, 5]
random.shuffle(numbers)      # 제자리에서 섞고 None 반환

재현 가능한 결과를 위해서는(테스트와 데이터 과학에서 유용) 생성 전에 시드를 설정합니다:

python
random.seed(42)
random.randint(1, 100)   # 시드 42에 대해 항상 같은 값

같은 시드는 어떤 기계에서도 매번 동일한 시퀀스를 생성합니다.

math

math 모듈은 기본 산술 연산자를 넘어서는 더 고급 수학 연산을 추가합니다. 제곱근, 거듭제곱, 로그, 삼각함수, 그리고 pi나 무한대 같은 특수 값들이 모두 여기 있습니다.

math는 표준 수학 함수의 C 레벨 구현을 제공합니다. math.pow()는 항상 float를 반환하는 반면, Python의 ** 연산자는 정수 밑과 지수에 대해 int를 반환한다는 점에 유의하세요. math.log(x, base)는 임의의 밑에 대한 로그를 계산하고; math.log(x)는 자연 로그를 계산합니다.

math는 C <math.h> 라이브러리 함수를 래핑합니다. 이는 순수 Python 구현보다 빠르며 엣지 케이스(NaN, 무한대)를 올바르게 처리합니다. math.isnan()math.isinf()는 IEEE 754 특수 값을 체크합니다. 복소수의 경우 cmath가 대응되는 함수를 제공합니다. 배열 수준의 수학 연산에는 numpy가 표준 도구입니다.

python
import math

math.sqrt(25)        # 5.0
math.pow(2, 10)      # 1024.0 (2 ** 10과 동일하지만 항상 float 반환)
math.log(100, 10)    # 2.0 (밑 10 로그)
math.log(math.e)     # 1.0 (자연 로그)

math.sin(math.pi / 2)   # 1.0
math.cos(0)             # 1.0

math.ceil(3.2)    # 4
math.floor(3.9)   # 3
math.trunc(3.9)   # 3 (양수의 경우 int()와 동일)

math.inf          # 무한대
math.isnan(float("nan"))   # True
math.isinf(math.inf)       # True

datetime

datetime 모듈은 날짜와 시간을 다룹니다. datetime.now()는 현재 날짜와 시간을 알려줍니다. strftime()은 이를 문자열로 포맷합니다. strptime()은 문자열을 datetime으로 파싱합니다. timedelta는 더하거나 뺄 수 있는 기간을 나타냅니다.

datetime, date, timedelta가 주요 클래스입니다. strftime()은 포맷 코드를 사용해 datetime을 문자열로 포맷합니다. strptime()은 주어진 포맷 패턴으로 문자열을 파싱합니다. timedelta는 산술 연산을 지원합니다: 날짜에 기간을 더하거나 빼고, <, >, -로 datetime을 비교할 수 있습니다.

datetime 객체는 기본적으로 naive(타임존 정보 없음)입니다. 타임존을 인식하는 datetime의 경우, datetime.now(tz=timezone.utc)나 오프셋이 포함된 datetime.fromisoformat()을 사용하세요. strftime/strptime은 C 라이브러리 포맷 코드를 사용합니다; %f는 마이크로초를 제공합니다. 고정밀 타이밍에는 datetime.now()보다 time.perf_counter()를 선호하세요. zoneinfo 모듈(Python 3.9+)은 IANA 타임존 지원을 제공합니다.

python
from datetime import datetime, date, timedelta

now   = datetime.now()           # 현재 날짜와 시간
today = date.today()             # 현재 날짜만

print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)

# 포맷팅
print(now.strftime("%Y-%m-%d"))           # "2024-01-15"
print(now.strftime("%d %B %Y, %H:%M"))   # "15 January 2024, 09:42"

# 파싱
deadline = datetime.strptime("2024-12-31", "%Y-%m-%d")

# 산술 연산
tomorrow    = today + timedelta(days=1)
next_week   = today + timedelta(weeks=1)
diff        = deadline - now
print(f"{diff.days} days until deadline")

자주 사용되는 strftime 코드:

코드의미예시
%Y4자리 연도2024
%m월 (0 패딩)01
%d일 (0 패딩)15
%H시 (24시간)09
%M42
%B전체 월 이름January

os와 pathlib

pathlib은 파일 경로를 다루는 현대적인 방법입니다. Path 객체를 사용하면 / 연산자로 경로를 구축하고, 검사하고, 탐색할 수 있습니다. os는 환경 변수와 더 저수준의 OS 연산에 대한 접근을 제공합니다. 새 코드에는 pathlib을 선호하세요.

pathlib.Path는 파일 시스템 경로를 쿼리하고 탐색하는 메서드를 가진 객체로 표현합니다. / 연산자는 경로 구성요소를 깔끔하게 연결하며, OS별 구분자를 자동으로 처리합니다. os.environ은 환경 변수를 위한 dict와 유사한 객체입니다; os.environ.get("KEY", "default")는 누락된 변수에 대해 안전합니다.

pathlib.Path는 각 OS별로 PurePosixPathPureWindowsPath라는 구체 구현을 가진 추상 베이스입니다. .glob(), .rglob(), .iterdir() 같은 메서드는 제너레이터를 반환합니다. .stat()os.stat()을 호출하고 stat_result를 반환합니다. os.path 함수들은 Python 3.6 이후 문자열과 Path 객체를 모두 받습니다. 새 코드에는 pathlib을 선호하세요; Path를 받지 않는 API를 호출할 때는 os.fspath()를 사용해 Pathstr로 변환하세요.

python
from pathlib import Path

p = Path("data/reports")

p.exists()           # 경로가 존재하면 True
p.is_dir()           # 디렉터리면 True
p.is_file()          # 파일이면 True

p.mkdir(parents=True, exist_ok=True)   # 디렉터리 생성

for f in p.glob("*.csv"):              # 디렉터리 내 모든 CSV 파일
    print(f.name)                      # 파일 이름만

report = p / "report_jan.csv"          # / 연산자가 경로를 결합
report.stem       # "report_jan" (확장자 없는 이름)
report.suffix     # ".csv"
report.parent     # Path("data/reports")

content = report.read_text()           # 파일 내용을 직접 읽기
report.write_text("new content\n")    # 직접 쓰기

os 모듈의 경우:

python
import os

os.getcwd()                        # 현재 작업 디렉터리
os.listdir(".")                    # 디렉터리 내용 목록
os.path.exists("data.txt")        # 경로가 존재하면 True
os.path.join("data", "file.txt")  # "data/file.txt" (크로스플랫폼)
os.environ.get("HOME")            # 환경 변수 읽기

새 코드에는 pathlib을 선호하세요. 환경 변수가 필요하거나 문자열을 기대하는 오래된 API를 다룰 때 os를 사용하세요.

timeit

timeit은 코드 실행에 걸리는 시간을 측정합니다. 두 가지 접근 방식을 비교하여 더 빠른 것을 선택하고 싶을 때 유용합니다. 안정적인 측정값을 얻으려면 코드를 여러 번 실행하세요.

timeit.timeit(stmt, setup, number)stmtnumber번 실행하여 시간을 측정하고 총 경과 시간을 초 단위로 반환합니다. setup 문자열은 측정 루프 전에 한 번 실행됩니다. 결과를 number로 나누면 호출당 시간을 얻을 수 있습니다. 반복 횟수가 많을수록 시스템 스케줄링으로 인한 노이즈가 줄어듭니다.

timeit은 노이즈를 줄이기 위해 측정 중 가비지 컬렉터를 비활성화합니다. 고해상도 측정을 위해 time.perf_counter()를 사용합니다. globals 매개변수는 측정할 문장에 네임스페이스를 전달합니다. 마이크로벤치마크의 경우 timeit이 표준 도구이고; 더 큰 프로그램에서 시간이 어디에 소요되는지 프로파일링하려면 cProfile을 사용하세요.

python
import timeit

# 단일 문장의 시간 측정
timeit.timeit("sum(range(1000))", number=10000)

# 더 복잡한 블록의 시간 측정
setup = "data = list(range(1000))"
code  = "[x * 2 for x in data]"
time  = timeit.timeit(code, setup=setup, number=10000)
print(f"{time:.4f} seconds for 10,000 runs")

number는 반복할 횟수입니다. 반복이 많을수록 더 안정적인 측정값이 나옵니다.

string

string 모듈은 문자, 숫자, 구두점에 대한 미리 정의된 문자열 상수를 제공합니다. 문자를 확인하거나 특정 알파벳에서 무작위 문자열을 생성해야 할 때 유용합니다.

string 모듈 상수(ascii_letters, digits, punctuation)는 인덱싱하고, 순회하고, in과 함께 사용할 수 있는 일반 문자열입니다. 이를 random.choices()와 결합하는 것이 무작위 토큰이나 비밀번호를 생성하는 표준적인 방법입니다.

string 모듈 상수는 특별한 동작이 없는 순수 Python 문자열 리터럴입니다. 이들은 집합이 아니므로 in은 O(n)입니다; 빈번한 멤버십 테스트에는 set(string.digits)를 사용하세요. string.Formatterstring.Template은 각각 str.format()$-스타일 치환의 기반 메커니즘입니다.

python
import string

string.ascii_lowercase   # "abcdefghijklmnopqrstuvwxyz"
string.ascii_uppercase   # "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
string.ascii_letters     # 둘을 결합
string.digits            # "0123456789"
string.punctuation       # 모든 구두점 문자

문자를 확인하거나 무작위 문자열을 생성해야 할 때 유용합니다:

python
import string, random

chars    = string.ascii_letters + string.digits
password = "".join(random.choices(chars, k=12))

직접 모듈 만들기

모든 Python 파일이 모듈입니다. 다른 파일에서 사용하려면 파일 이름(.py 제외)으로 가져옵니다. 모듈 전체를 가져와 점 표기법으로 그 내용을 사용하거나, 특정 이름을 직접 가져올 수 있습니다.

Python이 모듈을 임포트할 때, 파일을 위에서 아래로 한 번 실행하고 결과를 sys.modules에 캐싱합니다. 같은 모듈을 이후에 다시 임포트하면 파일을 다시 실행하지 않고 캐싱된 객체를 반환합니다. 더 큰 프로젝트의 경우 모듈은 패키지로 구성됩니다: __init__.py 파일이 있는 디렉터리입니다.

Import 해결은 sys.path를 사용합니다: 순서대로 검색되는 디렉터리 목록입니다. sys.path[0]은 스크립트의 디렉터리입니다. PYTHONPATH 환경 변수는 추가 디렉터리를 앞에 붙입니다. 패키지로 인식되려면 __init__.py(비어 있어도 됨)가 필요합니다. 상대 임포트(from . import module)는 패키지 내에서 유효합니다. importlib.reload()는 모듈을 다시 실행하지만, 이전 객체에 대한 기존 참조는 업데이트되지 않습니다.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

PI = 3.14159
python
# main.py
import utils

utils.clamp(150, 0, 100)   # 100
utils.PI                    # 3.14159

from utils import clamp
clamp(50, 0, 100)           # 50

Python은 임포트하는 파일과 같은 디렉터리(그리고 몇 군데 다른 곳)에서 모듈을 찾습니다. 더 큰 프로젝트의 경우 모듈은 패키지로 구성됩니다: __init__.py 파일이 있는 디렉터리입니다.

__name__ == "__main__"

Python이 파일을 직접 실행할 때 __name__"__main__"으로 설정됩니다. 같은 파일이 모듈로 임포트될 때는 __name__이 모듈 이름입니다. 이 패턴을 사용하면 파일을 직접 실행할 때 동작하지만 다른 모듈에서 임포트할 때는 건너뛰는 코드를 작성할 수 있습니다.

if __name__ == "__main__":은 실행 가능한 모듈 코드를 위한 표준 가드입니다. 이를 통해 모듈을 임포트 가능(함수를 노출)하면서 동시에 직접 실행 가능(테스트나 데모 코드와 함께)하게 만들 수 있습니다. 이것이 없으면 모듈을 임포트할 때 최상위 수준의 코드가 모두 실행되어 거의 항상 원치 않는 동작이 발생합니다.

__name__은 import 메커니즘에 의해 설정됩니다: 진입점 스크립트의 경우 "__main__", 그렇지 않으면 모듈의 점으로 구분된 이름입니다. 가드는 부수 효과(시작 코드, 인자 파싱, 테스트 실행)가 임포트 시 실행되는 것을 방지합니다. 명령줄 도구의 경우 진입점 로직을 main() 함수에 넣고 가드 아래에서 호출하는 것이 관용적인 패턴입니다.

python
# utils.py
def clamp(value, lo, hi):
    return max(lo, min(value, hi))

if __name__ == "__main__":
    # 이는 다음을 실행할 때만 동작: python utils.py
    # 다음을 할 때는 동작하지 않음: import utils
    print(clamp(150, 0, 100))   # 100

이는 독립 스크립트로도 유용한 모든 모듈에 대한 표준 패턴입니다.

표준 라이브러리 주요 모듈

알아두면 좋은 몇 가지 모듈이 더 있습니다. 각각 직접 구현하려면 상당한 작업이 필요한 일반적인 문제를 해결합니다.

표준 라이브러리는 광범위합니다; 아래 주요 모듈들은 프로덕션 코드에서 가장 자주 접하게 될 것들입니다. 완전한 레퍼런스는 docs.python.org/3/library가 권위 있는 출처입니다.

표준 라이브러리는 잘 테스트되고 문서화된 모듈들의 엄선된 집합입니다. 서드파티 패키지를 찾기 전에 표준 라이브러리에 해결책이 있는지 확인하세요: functools, itertools, contextlib, dataclasses, typing, abc 각각은 서드파티 패키지가 종종 재발명하는 도구들을 제공합니다.

collections: 특수화된 컨테이너 타입:

python
from collections import Counter, defaultdict, deque

Counter(["a", "b", "a", "c", "a"])   # Counter({'a': 3, 'b': 1, 'c': 1})
defaultdict(list)                      # 누락된 키를 자동 생성하는 dict
deque([1, 2, 3], maxlen=5)            # 양쪽 끝에서 빠른 append/pop

itertools: 이터러블 작업을 위한 도구:

python
import itertools

list(itertools.chain([1, 2], [3, 4]))          # [1, 2, 3, 4]
list(itertools.islice(range(100), 5))          # [0, 1, 2, 3, 4]
list(itertools.combinations([1, 2, 3], 2))     # [(1, 2), (1, 3), (2, 3)]
list(itertools.product([0, 1], repeat=2))      # [(0,0), (0,1), (1,0), (1,1)]

sys: Python 인터프리터에 대한 접근:

python
import sys

sys.argv        # 명령줄 인자 목록
sys.exit(1)     # 상태 코드와 함께 종료
sys.version     # Python 버전 문자열

서드파티 패키지: 표준 라이브러리를 넘어, pip로 커뮤니티 패키지를 설치합니다:

bash
pip install requests    # HTTP 라이브러리
pip install pandas      # 데이터 조작
pip install numpy       # 수치 계산

서드파티 패키지는 이 가이드의 범위를 벗어나지만, 패턴은 항상 같습니다: pip installimport.

실전 예제

random, string, datetime을 결합하여 타임스탬프가 포함된 고유 게임 ID 생성:

python
import random
import string
from datetime import datetime

def generate_game_id(length: int = 8) -> str:
    chars = string.ascii_uppercase + string.digits
    return "".join(random.choices(chars, k=length))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

game_id = generate_game_id()
print(f"[{timestamp()}] Starting game {game_id}")

scores = [random.randint(50, 100) for _ in range(5)]
print(f"Round scores: {scores}")
print(f"Best: {max(scores)}")

pathlibdatetime을 사용하여 디렉터리에서 파일을 찾고 크기 보고:

python
from pathlib import Path
from datetime import datetime

def find_files(directory: str, pattern: str = "*.csv") -> list[Path]:
    return sorted(Path(directory).glob(pattern))

def timestamp() -> str:
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

files = find_files(".", "*.md")[:3]
print(f"[{timestamp()}] Found {len(files)} file(s)")
for f in files:
    size = f.stat().st_size if f.exists() else 0
    print(f"  {f.name} ({size} bytes)")

타입이 지정된 기본값으로 환경 변수에서 앱 설정을 읽고, 구조화된 액세스 로그 항목을 newline-delimited JSON으로 작성:

python
import os
import json
from datetime import datetime
from pathlib import Path

def load_env_config() -> dict:
    return {
        "debug":     os.environ.get("DEBUG", "false").lower() == "true",
        "port":      int(os.environ.get("PORT", "8080")),
        "log_level": os.environ.get("LOG_LEVEL", "INFO"),
    }

def write_access_log(method: str, path: str, status: int) -> None:
    log_dir = Path("logs")
    log_dir.mkdir(exist_ok=True)
    entry = {
        "ts":     datetime.now().isoformat(),
        "method": method,
        "path":   path,
        "status": status,
    }
    with open(log_dir / "access.jsonl", "a") as f:
        f.write(json.dumps(entry) + "\n")

config = load_env_config()
print(f"Starting on port {config['port']}, debug={config['debug']}")
write_access_log("GET", "/users", 200)

Newline-delimited JSON(.jsonl)은 일반적인 로그 형식입니다: 각 줄이 유효한 JSON 객체이므로, 전체 파일을 로드하지 않고도 스트리밍, 추가, 줄 단위 파싱이 쉬워집니다.