Python 패키지와 모듈

모듈 vs 패키지

# 모듈 (Module): 단일 .py 파일
# math_utils.py
def add(a, b):
    return a + b

# 패키지 (Package): __init__.py를 포함한 디렉토리
# mypackage/
#   __init__.py
#   module1.py
#   module2.py
구분모듈패키지
형태단일 .py 파일__init__.py를 포함한 디렉토리
예시math_utils.pymypackage/
용도관련 함수/클래스 그룹화관련 모듈들을 계층적으로 구성

import 기본 문법

# 1. 모듈 전체 import
import math
math.sqrt(16)  # 4.0

# 2. 특정 함수/클래스만 import
from math import sqrt, pi
sqrt(16)  # 4.0 (math. 접두사 불필요)

# 3. 별칭(alias) 사용
import numpy as np
from datetime import datetime as dt

# 4. 모든 것 import (권장하지 않음)
from math import *  # 네임스페이스 오염 위험

# 5. 상대 import (패키지 내부에서)
from .module1 import func1      # 같은 패키지
from ..parent import func2      # 상위 패키지
from .subpackage.module import func3  # 하위 패키지

패키지 구조 예시

mypackage/
├── __init__.py          # 패키지 초기화 파일
├── module1.py
├── module2.py
└── subpackage/
    ├── __init__.py
    └── module3.py

init.py 파일

기본 역할

# mypackage/__init__.py

# 1. 디렉토리를 패키지로 인식시킴 (Python 3.3+ 에서는 선택사항)
#    하지만 명시적으로 작성하는 것이 좋음

# 2. 패키지 초기화 코드 실행
print("mypackage가 로드되었습니다")

# 3. 패키지 레벨에서 접근 가능한 것들 정의
from .module1 import Class1, function1
from .module2 import Class2

# 4. 패키지 버전 등 메타데이터 정의
__version__ = "1.0.0"
__author__ = "Your Name"

init.py 활용 예시

# mypackage/__init__.py

# 방법 1: 명시적으로 import
from .module1 import Class1
from .module2 import function2

# 사용자가 간단하게 사용 가능
# from mypackage import Class1, function2

# 방법 2: 서브모듈 노출
from . import module1
from . import module2

# 사용자가 사용
# import mypackage
# mypackage.module1.Class1()

# 방법 3: 패키지 초기화
def init_package():
    """패키지 초기화 함수"""
    print("패키지 초기화 중...")

# 패키지 import 시 자동 실행
init_package()

all 변수

목적: from package import * 시 제어

# mypackage/__init__.py

# __all__이 없으면: 모든 공개 이름이 import됨
# __all__이 있으면: __all__에 명시된 것만 import됨

__all__ = ['Class1', 'function1', 'CONSTANT']

from .module1 import Class1, Class2  # Class2는 __all__에 없음
from .module2 import function1, function2  # function2는 __all__에 없음
from .module3 import CONSTANT

# 사용 예시
# from mypackage import *  # Class1, function1, CONSTANT만 import됨

all 사용 예시

# mypackage/__init__.py

# 공개 API만 명시적으로 정의
__all__ = [
    'User',
    'create_user',
    'DEFAULT_CONFIG',
]

# 내부 구현은 숨김
from .models import User, AdminUser  # AdminUser는 공개하지 않음
from .utils import create_user, _internal_helper  # _internal_helper는 숨김
from .config import DEFAULT_CONFIG, _SECRET_KEY  # _SECRET_KEY는 숨김

# 사용자 관점
# from mypackage import *  # User, create_user, DEFAULT_CONFIG만 보임

패키지 생성 실전 예시

1. 기본 패키지 구조

calculator/
├── __init__.py
├── basic.py
├── advanced.py
└── utils.py
# calculator/__init__.py
"""간단한 계산기 패키지"""

__version__ = "1.0.0"
__all__ = ['add', 'subtract', 'multiply', 'divide', 'power']

from .basic import add, subtract, multiply, divide
from .advanced import power

# calculator/basic.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

# calculator/advanced.py
def power(base, exp):
    return base ** exp

# 사용 예시
from calculator import add, power
result = add(5, 3)  # 8
result = power(2, 3)  # 8

2. 계층적 패키지 구조

mylib/
├── __init__.py
├── math/
│   ├── __init__.py
│   ├── basic.py
│   └── advanced.py
├── string/
│   ├── __init__.py
│   └── utils.py
└── io/
    ├── __init__.py
    └── file_handler.py
# mylib/__init__.py
from . import math
from . import string
from . import io

# mylib/math/__init__.py
from .basic import add, subtract
from .advanced import integrate, differentiate

# 사용 예시
from mylib.math import add
from mylib.string.utils import capitalize

패키지 배포 방법

1. 기본 파일 구조

mypackage/
├── mypackage/          # 실제 패키지 코드
│   ├── __init__.py
│   ├── module1.py
│   └── module2.py
├── tests/              # 테스트 코드
│   └── test_module1.py
├── README.md           # 프로젝트 설명
├── LICENSE             # 라이선스
├── setup.py            # 빌드 설정 (구식 방법)
└── pyproject.toml      # 빌드 설정 (현대적 방법)

2. pyproject.toml 설정 (권장)

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mypackage"
version = "1.0.0"
description = "간단한 패키지 설명"
readme = "README.md"
requires-python = ">=3.8"
license = {text = "MIT"}
authors = [
    {name = "Your Name", email = "your.email@example.com"}
]
keywords = ["package", "example"]
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]

dependencies = [
    "requests>=2.28.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=22.0",
]

[project.urls]
Homepage = "https://github.com/username/mypackage"
Documentation = "https://github.com/username/mypackage#readme"
Repository = "https://github.com/username/mypackage"

[tool.setuptools.packages.find]
where = ["."]
include = ["mypackage*"]

3. setup.py 설정 (구식 방법)

from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="1.0.0",
    author="Your Name",
    author_email="your.email@example.com",
    description="간단한 패키지 설명",
    long_description=open("README.md").read(),
    long_description_content_type="text/markdown",
    url="https://github.com/username/mypackage",
    packages=find_packages(),
    classifiers=[
        "Programming Language :: Python :: 3",
        "License :: OSI Approved :: MIT License",
        "Operating System :: OS Independent",
    ],
    python_requires=">=3.8",
    install_requires=[
        "requests>=2.28.0",
    ],
    extras_require={
        "dev": ["pytest>=7.0", "black>=22.0"],
    },
)

4. 빌드 및 배포

# 1. 빌드 도구 설치
pip install build twine

# 2. 소스 배포판과 휠 생성
python -m build

# 3. 생성된 파일 확인
# dist/
#   ├── mypackage-1.0.0.tar.gz      # 소스 배포판
#   └── mypackage-1.0.0-py3-none-any.whl  # 휠 배포판

# 4. PyPI에 업로드 (테스트 서버)
python -m twine upload --repository testpypi dist/*

# 5. PyPI에 업로드 (실제 서버)
python -m twine upload dist/*

# 6. 설치 확인
pip install mypackage

5. 로컬 설치 (개발 중)

# 개발 모드로 설치 (코드 변경 시 자동 반영)
pip install -e .

# 또는
pip install -e ".[dev]"  # 개발 의존성 포함

네임스페이스 패키지 (Python 3.3+)

# __init__.py 없이도 패키지로 인식 가능
# 여러 디렉토리에 분산된 패키지를 하나로 통합

# site-packages/pkg1/
#   module1.py

# site-packages/pkg2/
#   module2.py

# 두 디렉토리가 하나의 패키지로 인식됨
import pkg1.module1
import pkg2.module2

import 최적화 팁

# 1. 필요한 것만 import
from math import sqrt  # 좋음
import math            # 나쁨 (전체 모듈 로드)

# 2. 순환 import 방지
# module1.py
from .module2 import func2  # 나쁨 (순환 참조 위험)

# module1.py
def func1():
    from .module2 import func2  # 좋음 (지연 import)
    return func2()

# 3. 조건부 import
try:
    import numpy as np
    HAS_NUMPY = True
except ImportError:
    HAS_NUMPY = False

# 4. 타입 체크용 import
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from .module2 import Class2  # 타입 체크에만 사용, 런타임에는 로드 안됨

실전 패턴

# 패키지 초기화 시 설정 로드
# mypackage/__init__.py
import os
from pathlib import Path

# 패키지 루트 경로
PACKAGE_ROOT = Path(__file__).parent

# 설정 파일 경로
CONFIG_PATH = PACKAGE_ROOT / "config.json"

# 리소스 파일 접근
def get_resource_path(relative_path):
    return PACKAGE_ROOT / "resources" / relative_path

# 버전 관리
__version__ = "1.0.0"

# 로깅 설정
import logging
logger = logging.getLogger(__name__)

패키지 배포 체크리스트

  • pyproject.toml 또는 setup.py 작성
  • README.md 작성 (설치 방법, 사용 예시)
  • LICENSE 파일 추가
  • 버전 번호 관리 (__version__)
  • 의존성 명시 (install_requires)
  • 테스트 코드 작성
  • .gitignoredist/, build/, *.egg-info/ 추가
  • python -m build로 빌드 테스트
  • pip install .로 로컬 설치 테스트
  • PyPI 계정 생성 및 업로드