Skip to main content

모듈과 패키지

모듈이란?

모듈은 파이썬 코드가 담긴 파일(.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)

자주 묻는 질문

Q1. import와 from import 중 무엇을 써야 하나요?

A: 상황에 따라 선택하세요

# import: 네임스페이스 명확
import math
print(math.sqrt(16)) # 어디서 왔는지 명확

# from import: 간결함
from math import sqrt
print(sqrt(16)) # 간결하지만 출처 불명확

# 추천
# 1. 표준 라이브러리: import
# 2. 자주 쓰는 함수: from import
# 3. 이름 충돌 가능성: import

Q2. 패키지 구조는 어떻게 만들어야 하나요?

A: 논리적 단위로 분리

# 좋은 구조
myapp/
├── models/ # 데이터 모델
├── services/ # 비즈니스 로직
├── controllers/ # 요청 처리
├── utils/ # 유틸리티
└── tests/ # 테스트

# 나쁜 구조
myapp/
├── file1.py
├── file2.py
├── file3.py
└── ... # 모든 코드가 한 곳에

Q3. init.py는 꼭 필요한가요?

A: Python 3.3+부터는 선택사항

# Python 3.3+ : Namespace Package
# __init__.py 없어도 패키지로 인식

# 하지만 권장사항
# 1. 명시적으로 패키지임을 표시
# 2. 패키지 초기화 코드 실행
# 3. 편의성 import 제공
# 4. __all__ 정의

# __init__.py 예시
from .module1 import func1
from .module2 import func2

__version__ = "1.0.0"
__all__ = ['func1', 'func2']

Q4. 순환 import는 어떻게 해결하나요?

A: 구조를 개선하거나 지연 import

# 순환 import 문제
# module_a.py
from module_b import func_b

def func_a():
func_b()

# module_b.py
from module_a import func_a # 순환!

def func_b():
func_a()

# 해결 방법 1: 구조 개선
# common.py
def shared_function():
pass

# module_a.py
from common import shared_function

# module_b.py
from common import shared_function

# 해결 방법 2: 지연 import
# module_b.py
def func_b():
from module_a import func_a # 함수 안에서
func_a()

다음 단계

모듈과 패키지를 마스터했습니다!

핵심 정리:
✅ 모듈 import와 from import
✅ __name__과 __main__ 패턴
✅ 패키지 구조와 __init__.py
✅ 가상환경과 pip
✅ requirements.txt로 의존성 관리
✅ 실전 프로젝트 구조

다음 단계: Python Basics 시리즈를 완료했습니다! 이제 Python Practical 시리즈에서 실전 프로젝트를 만들어보세요!