모듈과 패키지
모듈이란?
모듈은 파이썬 코드가 담긴 파일(.py)입니다. 코드를 재사용하고 구조화하는 핵심 도구입니다.
# math_utils.py
def add(a, b):
"""두 수를 더합니다"""
return a + b
def multiply(a, b):
"""두 수를 곱합니다"""
return a * b
PI = 3.14159
# main.py
import math_utils
result = math_utils.add(3, 5)
print(result) # 8
print(math_utils.PI) # 3.14159
import 문
기본 import
# 전체 모듈 import
import math
print(math.sqrt(16)) # 4.0
print(math.pi) # 3.141592653589793
# 여러 모듈 import
import os
import sys
import json
# 별칭 사용
import math as m
import datetime as dt
print(m.sqrt(25)) # 5.0
now = dt.datetime.now()
from import
# 특정 항목만 import
from math import sqrt, pi
print(sqrt(16)) # 4.0 (math. 없이 사용)
print(pi) # 3.141592653589793
# 여러 항목
from os import path, getcwd, listdir
# 별칭과 함께
from datetime import datetime as dt
now = dt.now()
print(now)
# 모든 항목 import (권장 안 함)
from math import *
print(sqrt(16)) # 4.0
print(sin(0)) # 0.0
# ⚠️ 문제: 이름 충돌 가능
# from module1 import *
# from module2 import * # 같은 이름이 있으면 덮어씀
상대 import vs 절대 import
# 프로젝트 구조
# myproject/
# ├ ── main.py
# ├── utils/
# │ ├── __init__.py
# │ ├── math_utils.py
# │ └── string_utils.py
# └── models/
# ├── __init__.py
# └── user.py
# 절대 import (권장)
from utils.math_utils import add
from models.user import User
# 상대 import (패키지 내부에서만)
# utils/string_utils.py 안에서
from .math_utils import add # 같은 디렉토리
from ..models.user import User # 상위 디렉토리
# . : 현재 디렉토리
# .. : 상위 디렉토리
# ... : 상위의 상위
자신만의 모듈 만들기
간단한 모듈
# calculator.py
"""
간단한 계산기 모듈
이 모듈은 기본적인 사칙연산 함수를 제공합니다.
"""
def add(a, b):
"""두 수를 더합니다"""
return a + b
def subtract(a, b):
"""두 수를 뺍니다"""
return a - b
def multiply(a, b):
"""두 수를 곱합니다"""
return a * b
def divide(a, b):
"""두 수를 나눕니다"""
if b == 0:
raise ValueError("0으로 나눌 수 없습니다")
return a / b
# 모듈 변수
VERSION = "1.0.0"
AUTHOR = "홍길동"
# 사용 (다른 파일에서)
# import calculator
# print(calculator.add(3, 5))
# print(calculator.VERSION)
모듈 속성
# my_module.py
"""모듈 설명"""
def my_function():
"""함수 설명"""
pass
class MyClass:
"""클래스 설명"""
pass
# 모듈 속성 확인
import my_module
print(my_module.__name__) # my_module
print(my_module.__file__) # 파일 경로
print(my_module.__doc__) # 모듈 설명
print(dir(my_module)) # 모든 속성 목록
# 함수와 클래스 확인
print(my_module.my_function.__doc__) # 함수 설명
print(my_module.MyClass.__doc__) # 클래스 설명
__name__과 main
스크립트 vs 모듈
# my_module.py
def greet(name):
return f"Hello, {name}!"
def main():
print(greet("World"))
print(greet("Python"))
# 이 파일이 직접 실행될 때만 main() 실행
if __name__ == "__main__":
main()
# 사용 예시:
# 1. 직접 실행
# $ python my_module.py
# Hello, World!
# Hello, Python!
# 2. 모듈로 import
# import my_module
# print(my_module.greet("Alice")) # Hello, Alice!
# (main()은 실행 안 됨)
실용적인 패턴
# data_processor.py
def process_data(data):
"""데이터 처리"""
return [x * 2 for x in data]
def load_data(filename):
"""데이터 로드"""
# 파일에서 데이터 읽기
return [1, 2, 3, 4, 5]
def save_data(data, filename):
"""데이터 저장"""
# 파일에 데이터 쓰기
print(f"Saved {len(data)} items to {filename}")
def main():
"""메인 실행 로직"""
# 스크립트로 실행할 때의 동작
data = load_data("input.txt")
processed = process_data(data)
save_data(processed, "output.txt")
print("처리 완료!")
if __name__ == "__main__":
main()
# 다른 파일에서 import
# from data_processor import process_data
# result = process_data([1, 2, 3])
패키지 (Package)
패키지 구조
# 패키지는 __init__.py가 있는 디렉토리
# mypackage/
# ├── __init__.py
# ├── module1.py
# ├── module2.py
# └── subpackage/
# ├── __init__.py
# └── module3.py
# module1.py
def func1():
return "Function 1"
# module2.py
def func2():
return "Function 2"
# subpackage/module3.py
def func3():
return "Function 3"
# 사용
import mypackage.module1
from mypackage import module2
from mypackage.subpackage import module3
print(mypackage.module1.func1()) # Function 1
print(module2.func2()) # Function 2
print(module3.func3()) # Function 3
init.py
# mypackage/__init__.py
# 1. 빈 파일 (패키지임을 표시만)
# (Python 3.3+에서는 없어도 되지만 권장)
# 2. 패키지 초기화 코드
print("mypackage를 import했습니다")
# 3. 편의성을 위한 import
from .module1 import func1
from .module2 import func2
# 4. __all__ 정의 (from package import * 시)
__all__ = ['func1', 'func2']
# 5. 패키지 버전 정보
__version__ = "1.0.0"
__author__ = "홍길동"
# 사용
import mypackage
print(mypackage.__version__) # 1.0.0
print(mypackage.func1()) # 바로 접근 가능
실전 패키지 예제
# utils/
# ├── __init__.py
# ├── string_utils.py
# ├── math_utils.py
# └── file_utils.py
# utils/__init__.py
"""유틸리티 패키지"""
from .string_utils import (
capitalize_words,
reverse_string,
count_words
)
from .math_utils import (
factorial,
fibonacci,
is_prime
)
from .file_utils import (
read_file,
write_file,
file_exists
)
__version__ = "1.0.0"
__all__ = [
'capitalize_words', 'reverse_string', 'count_words',
'factorial', 'fibonacci', 'is_prime',
'read_file', 'write_file', 'file_exists'
]
# utils/string_utils.py
def capitalize_words(text):
"""각 단어의 첫 글자를 대문자로"""
return ' '.join(word.capitalize() for word in text.split())
def reverse_string(text):
"""문자열 역순"""
return text[::-1]
def count_words(text):
"""단어 수 세기"""
return len(text.split())
# utils/math_utils.py
def factorial(n):
"""팩토리얼"""
if n <= 1:
return 1
return n * factorial(n - 1)
def fibonacci(n):
"""피보나치 수"""
if n <= 1:
return n
a, b = 0, 1
for _ in range(2, n + 1):
a, b = b, a + b
return b
def is_prime(n):
"""소수 판별"""
if n < 2:
return False
for i in range(2, int(n ** 0.5) + 1):
if n % i == 0:
return False
return True
# utils/file_utils.py
def read_file(filename):
"""파일 읽기"""
try:
with open(filename, 'r', encoding='utf-8') as f:
return f.read()
except FileNotFoundError:
return None
def write_file(filename, content):
"""파일 쓰기"""
with open(filename, 'w', encoding='utf-8') as f:
f.write(content)
def file_exists(filename):
"""파일 존재 여부"""
import os
return os.path.exists(filename)
# 사용
from utils import capitalize_words, factorial, read_file
print(capitalize_words("hello world")) # Hello World
print(factorial(5)) # 120
표준 라이브러리
자주 사용하는 모듈
# os - 운영체제 인터페이스
import os
print(os.getcwd()) # 현재 디렉토리
print(os.listdir('.')) # 파일 목록
# os.mkdir('new_folder') # 디렉토리 생성
# os.remove('file.txt') # 파일 삭제
# sys - 시스템 관련
import sys
print(sys.version) # 파이썬 버전
print(sys.platform) # 플랫폼
print(sys.argv) # 명령줄 인자
# datetime - 날짜와 시간
from datetime import datetime, timedelta
now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))
tomorrow = now + timedelta(days=1)
print(tomorrow)
# json - JSON 처리
import json
data = {"name": "홍길동", "age": 25}
json_str = json.dumps(data, ensure_ascii=False)
print(json_str) # {"name": "홍길동", "age": 25}
parsed = json.loads(json_str)
print(parsed["name"]) # 홍길동
# random - 난수 생성
import random
print(random.randint(1, 100)) # 1~100 정수
print(random.choice(['a', 'b', 'c'])) # 무작위 선택
print(random.random()) # 0~1 실수
# re - 정규표현식
import re
text = "이메일: hong@example.com"
match = re.search(r'[\w.-]+@[\w.-]+', text)
if match:
print(match.group()) # hong@example.com
# pathlib - 경로 처리 (Python 3.4+)
from pathlib import Path
path = Path('folder/file.txt')
print(path.name) # file.txt
print(path.suffix) # .txt
print(path.parent) # folder
가상환경 (Virtual Environment)
venv 사용하기
# 가상환경 생성
python -m venv myenv
# 활성화
# Windows
myenv\Scripts\activate
# macOS/Linux
source myenv/bin/activate
# 비활성화
deactivate
가상환경의 필요성
# 프로젝트별 독립적인 패키지 관리
# 프로젝트 A
# - Django 3.2
# - requests 2.25.0
# 프로젝트 B
# - Django 4.0
# - requests 2.28.0
# 가상환경 없이는 버전 충돌!
# 가상환경으로 각 프로젝트마다 독립적인 환경 구성
pip와 패키지 관리
pip 기본 사용
# 패키지 설치
pip install requests
pip install numpy pandas matplotlib
# 특정 버전 설치
pip install requests==2.28.0
pip install Django>=4.0
# 업그레이드
pip install --upgrade requests
# 삭제
pip uninstall requests
# 설치된 패키지 목록
pip list
pip freeze
# 패키지 정보
pip show requests
requirements.txt
# requirements.txt
requests==2.28.0
numpy>=1.20.0
pandas==1.4.0
matplotlib>=3.5.0
Django==4.0.0
# requirements.txt에서 설치
pip install -r requirements.txt
# 현재 환경을 requirements.txt로 저장
pip freeze > requirements.txt
# 개발 환경 분리
# requirements-dev.txt
# pytest==7.0.0
# black==22.0.0
# mypy==0.950
pip install -r requirements-dev.txt
모듈 검색 경로
sys.path
import sys
# 모듈 검색 경로 확인
for path in sys.path:
print(path)
# 경로 추가
sys.path.append('/path/to/my/modules')
# 모듈 위치 확인
import os
print(os.__file__) # os 모듈의 파일 위치
PYTHONPATH 환경변수
# 환경변수로 경로 추가
# Linux/macOS
export PYTHONPATH=/path/to/modules:$PYTHONPATH
# Windows
set PYTHONPATH=C:\path\to\modules;%PYTHONPATH%
# 영구적으로 설정
# Linux/macOS: ~/.bashrc 또는 ~/.zshrc
# Windows: 시스템 환경변수
실전 예제
프로젝트 구조
# myproject/
# ├── main.py
# ├── config.py
# ├── requirements.txt
# ├── utils/
# │ ├── __init__.py
# │ ├── logger.py
# │ └── validators.py
# ├── models/
# │ ├── __init__.py
# │ ├── user.py
# │ └── product.py
# └── services/
# ├── __init__.py
# ├── user_service.py
# └── product_service.py
# config.py
class Config:
"""설정"""
DEBUG = True
DATABASE_URL = "sqlite:///app.db"
SECRET_KEY = "secret-key-here"
# utils/logger.py
import logging
def get_logger(name):
"""로거 생성"""
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
handler = logging.StreamHandler()
formatter = logging.Formatter(
'%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
handler.setFormatter(formatter)
logger.addHandler(handler)
return logger
# utils/validators.py
import re
def validate_email(email):
"""이메일 검증"""
pattern = r'^[\w\.-]+@[\w\.-]+\.\w+$'
return re.match(pattern, email) is not None
def validate_phone(phone):
"""전화번호 검증"""
pattern = r'^\d{3}-\d{4}-\d{4}$'
return re.match(pattern, phone) is not None
# models/user.py
from utils.validators import validate_email
class User:
"""사용자 모델"""
def __init__(self, name, email):
if not validate_email(email):
raise ValueError("유효하지 않은 이메일")
self.name = name
self.email = email
def __str__(self):
return f"{self.name} <{self.email}>"
# services/user_service.py
from models.user import User
from utils.logger import get_logger
logger = get_logger(__name__)
class UserService:
"""사용자 서비스"""
def __init__(self):
self.users = []
def create_user(self, name, email):
"""사용자 생성"""
try:
user = User(name, email)
self.users.append(user)
logger.info(f"사용자 생성: {user}")
return user
except ValueError as e:
logger.error(f"사용자 생성 실패: {e}")
raise
def get_all_users(self):
"""모든 사용자 조회"""
return self.users
# main.py
from config import Config
from services.user_service import UserService
from utils.logger import get_logger
logger = get_logger(__name__)
def main():
logger.info(f"애플리케이션 시작 (DEBUG={Config.DEBUG})")
service = UserService()
# 사용자 생성
try:
service.create_user("홍길동", "hong@example.com")
service.create_user("김철수", "kim@example.com")
service.create_user("이 영희", "invalid-email") # 실패
except ValueError:
pass
# 사용자 목록
users = service.get_all_users()
logger.info(f"총 사용자 수: {len(users)}")
for user in users:
print(user)
if __name__ == "__main__":
main()
설정 모듈 패턴
# config/
# ├── __init__.py
# ├── base.py
# ├── development.py
# ├── production.py
# └── testing.py
# config/base.py
class BaseConfig:
"""기본 설정"""
SECRET_KEY = "base-secret-key"
DEBUG = False
TESTING = False
# config/development.py
from .base import BaseConfig
class DevelopmentConfig(BaseConfig):
"""개발 환경 설정"""
DEBUG = True
DATABASE_URL = "sqlite:///dev.db"
# config/production.py
from .base import BaseConfig
class ProductionConfig(BaseConfig):
"""프로덕션 환경 설정"""
DATABASE_URL = "postgresql://..."
# config/testing.py
from .base import BaseConfig
class TestingConfig(BaseConfig):
"""테스트 환경 설정"""
TESTING = True
DATABASE_URL = "sqlite:///:memory:"
# config/__init__.py
import os
from .development import DevelopmentConfig
from .production import ProductionConfig
from .testing import TestingConfig
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig
}
def get_config():
"""환경에 맞는 설정 반환"""
env = os.getenv('ENV', 'development')
return config[env]
# 사용
from config import get_config
Config = get_config()
print(Config.DEBUG)
print(Config.DATABASE_URL)