Skip to content

KR_Package

somaz edited this page Apr 21, 2025 · 5 revisions

Python 패키지(Package) 개념 정리


1️⃣ 패키지(Package)란?

패키지는 모듈들을 모아놓은 디렉터리로, 관련된 모듈들을 체계적으로 관리할 수 있게 해준다.

mypackage/
    __init__.py
    module1.py
    subpackage/
        __init__.py
        module2.py

패키지의 주요 특징:

  • 코드의 계층적 구조화: 관련 모듈을 논리적 그룹으로 조직
  • 네임스페이스 관리: 모듈 이름 충돌 방지
  • 코드 재사용성 향상: 기능별 분리와 모듈화
  • 배포 및 설치 용이: pip 등 패키지 관리자를 통한 배포 가능
  • 독립적인 개발 및 테스트: 패키지 단위로 개발 및 테스트 수행


2️⃣ 패키지 구조와 임포트

# 전체 모듈 임포트
import mypackage.module1

# 특정 함수만 임포트
from mypackage.module1 import function1

# 서브패키지의 모듈 임포트
from mypackage.subpackage import module2

효과적인 패키지 구조 설계:

  • 기능별 구분: 관련 기능은 같은 모듈이나 서브패키지로 그룹화
  • 적절한 깊이: 너무 깊은 계층은 피하고 2-3단계 이내로 유지
  • 일관된 명명 규칙: 모듈명은 소문자와 언더스코어 사용
  • 의존성 관리: 순환 참조를 피하고 명확한 의존성 계층 설계
  • 공개 API 정의: __init__.py에서 공개할 항목 명시적 정의


3️⃣ init.py 파일

# mypackage/__init__.py

# 버전 정보
VERSION = '1.0.0'

# 공개할 모듈 지정
__all__ = ['module1', 'module2']

# 패키지 초기화 코드
print("Initializing package...")

# 하위 모듈 자동 임포트
from .module1 import function1

init.py 활용 방법:

  • 패키지 식별: 디렉터리를 패키지로 인식하게 함 (Python 3.3+ 에서는 선택사항)
  • 패키지 초기화: 패키지 임포트 시 실행할 초기화 코드 포함
  • 공개 API 정의: __all__ 변수로 from package import * 시 임포트될 이름 지정
  • 서브모듈 자동 임포트: 편의를 위해 중요 서브모듈 내용 자동 임포트
  • 버전 관리: 패키지 버전 정보 제공
  • 문서화: 패키지 사용법에 대한 docstring 포함


4️⃣ 상대 경로 임포트

# 현재 디렉터리
from . import module1

# 부모 디렉터리
from .. import other_module

# 부모의 다른 패키지
from ..other_package import some_module

상대 경로 임포트 사용 시 주의사항:

  • 메인 스크립트에서는 상대 경로 임포트 사용 불가 (패키지 내 모듈에서만 사용)
  • 명시적인 상대 경로 사용 (암묵적 상대 경로는 Python 3에서 제거됨)
  • 과도한 상대 경로 임포트(... 등) 지양: 코드 이해도 저하
  • 상대 경로 임포트는 패키지 내 모듈 재구성 시 유리
  • from __future__ import absolute_import를 사용하여 명시적 임포트 강제 가능


5️⃣ 네임스페이스 패키지

# 여러 위치에 분산된 패키지
path1/mypackage/module1.py
path2/mypackage/module2.py

# PYTHONPATH에 path1과 path2 추가
import mypackage.module1
import mypackage.module2

네임스페이스 패키지의 특징과 장점:

  • __init__.py 불필요: Python 3.3+ 부터는 파일이 없어도 패키지 인식
  • 분산 배포 가능: 여러 위치에 같은 패키지명으로 코드 배포 가능
  • 동적 확장: 기존 패키지에 새 기능 추가 시 유리
  • 협업 개발: 여러 팀이 독립적으로 같은 패키지 확장 가능
  • 플러그인 구조: 확장 기능을 별도 배포하여 플러그인처럼 사용
# PEP 420 네임스페이스 패키지 상세 예제
# 디렉터리 구조:
# /path1/spam/module1.py
# /path2/spam/module2.py

# PYTHONPATH=/path1:/path2 python

import spam.module1  # /path1/spam/module1.py
import spam.module2  # /path2/spam/module2.py

# 패키지 경로 확인
import spam
print(spam.__path__)  # ['/path1/spam', '/path2/spam'] 같은 형태로 출력


6️⃣ 패키지 배포

# setup.py
from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="1.0.0",
    packages=find_packages(),
    install_requires=[
        'requests>=2.22.0',
    ],
)

패키지 배포 프로세스:

  • setup.py 작성: 패키지 메타데이터 및 의존성 정의
  • README.md: 사용법 및 기능 설명 문서 작성
  • LICENSE: 라이선스 파일 추가
  • 테스트 실행: 배포 전 모든 테스트 통과 확인
  • 패키지 빌드: python setup.py sdist bdist_wheel
  • PyPI 업로드: twine upload dist/*
  • 설치 확인: pip install mypackage


7️⃣ 현대적인 패키지 구조

현대적인 파이썬 패키지는 보다 체계적인 구조를 갖는다.

myproject/
│
├── src/
│   └── mypackage/
│       ├── __init__.py
│       ├── module1.py
│       ├── module2.py
│       └── subpackage/
│           ├── __init__.py
│           └── module3.py
│
├── tests/
│   ├── __init__.py
│   ├── test_module1.py
│   └── test_module2.py
│
├── docs/
│   ├── conf.py
│   ├── index.rst
│   └── api.rst
│
├── setup.py
├── pyproject.toml
├── setup.cfg
├── README.md
├── CHANGELOG.md
├── LICENSE
└── .gitignore

현대적 패키지 구조의 특징:

  • src/ 디렉터리: 실제 패키지 코드를 별도 디렉터리에 배치
  • 명확한 테스트 분리: tests/ 디렉터리에 테스트 코드 배치
  • 문서화: docs/ 디렉터리에 문서 파일 (Sphinx 등 사용)
  • 빌드 시스템: pyproject.toml로 빌드 요구사항 정의 (PEP 517/518)
  • 구성 파일 분리: setup.cfg로 설정 정보 관리
  • CI/CD 통합: .github/workflows/ 등 CI 구성 파일 포함
  • 개발 환경: requirements-dev.txt 또는 tox.ini 등 개발 의존성 관리


8️⃣ 패키지 관리 도구

파이썬 패키지를 관리하는 다양한 도구들이 있다.

# pip을 사용한 패키지 설치
pip install package-name

# 가상 환경 생성 및 활성화 (venv)
python -m venv myenv
source myenv/bin/activate  # Linux/Mac
myenv\Scripts\activate     # Windows

# Poetry를 사용한 의존성 관리
poetry init
poetry add requests
poetry install

# Conda를 사용한 환경 및 패키지 관리
conda create -n myenv python=3.9
conda activate myenv
conda install numpy pandas

주요 패키지 관리 도구:

  • pip: 기본 패키지 설치 도구, PyPI 저장소 사용
  • venv/virtualenv: 격리된 파이썬 환경 생성, 프로젝트별 의존성 관리
  • Poetry: 의존성 관리 및 패키지 빌드, 배포를 위한 현대적 도구
  • Pipenv: pip + virtualenv 결합, Pipfile로 의존성 관리
  • Conda: 과학 계산 중심 패키지 관리자, 비-파이썬 의존성도 관리
  • PDM: PEP 582 기반 현대적 패키지 관리자


9️⃣ 패키지 내부 데이터 파일 관리

패키지에는 종종 Python 코드 외의 데이터 파일(템플릿, 설정 파일 등)이 포함된다.

# 패키지 데이터 파일 포함 (setup.py)
setup(
    # ... 기타 설정 ...
    package_data={
        'mypackage': ['data/*.json', 'templates/*.html'],
    },
    # 또는
    include_package_data=True,  # MANIFEST.in 사용 시
)

# MANIFEST.in
include mypackage/data/*.json
include mypackage/templates/*.html

# 데이터 파일 접근 (코드에서)
import pkg_resources
data_path = pkg_resources.resource_filename('mypackage', 'data/config.json')

# Python 3.7+ 에서는 importlib.resources 사용 가능
from importlib import resources
with resources.path('mypackage.data', 'config.json') as path:
    config_path = path

데이터 파일 관리 방법:

  • package_data: setup.py에서 패키지에 포함될 데이터 파일 지정
  • MANIFEST.in: 소스 배포에 포함될 파일 패턴 정의
  • pkg_resources: 패키지 내 리소스 접근을 위한 유틸리티 (약간의 오버헤드)
  • importlib.resources: Python 3.7+ 에서 제공하는 현대적 리소스 접근 방법
  • 상대 경로 사용 지양: __file__ 기준 상대 경로는 배포 형태에 따라 문제 발생 가능


🔟 고급 패키지 기법

1. 지연 임포트 (Lazy Import)

# mypackage/__init__.py
def get_module1():
    import mypackage.module1
    return mypackage.module1

# 사용 시점에 임포트
def function_that_uses_module1():
    module1 = get_module1()
    return module1.some_function()

2. 플러그인 시스템 구현

# mypackage/plugins/__init__.py
import importlib
import pkgutil

def load_plugins():
    """모든 플러그인 모듈을 동적으로 로드"""
    plugins = {}
    
    # 현재 패키지(plugins)의 경로
    package_path = __path__
    prefix = __name__ + "."
    
    # 모든 하위 모듈 탐색
    for _, name, ispkg in pkgutil.iter_modules(package_path, prefix):
        if not ispkg:  # 패키지가 아닌 모듈만 처리
            module = importlib.import_module(name)
            if hasattr(module, 'register'):
                plugin_name = name.split('.')[-1]
                plugins[plugin_name] = module
    
    return plugins

# 사용 예:
plugins = load_plugins()
for name, plugin in plugins.items():
    plugin.register()

3. 네임스페이스 제어

# mypackage/__init__.py

# 공개 API만 노출
__all__ = ['public_function', 'PublicClass']

# 내부 모듈에서 필요한 것만 가져오기
from .module1 import public_function, PublicClass
from .module2 import _internal_function  # 언더스코어로 내부용 표시

# 서브패키지는 명시적으로 임포트해야 함
# 예: import mypackage.subpackage

4. 조건부 임포트 및 폴백(Fallback)

# 최적화된 구현이 있으면 사용, 없으면 순수 파이썬 구현 사용
try:
    from mypackage._fast_impl import fast_function
except ImportError:
    from mypackage._pure_impl import fast_function

# 버전별 호환성
import sys
if sys.version_info >= (3, 8):
    from importlib.metadata import version
else:
    from importlib_metadata import version

고급 패키지 기법 활용:

  • 지연 임포트: 시작 시간 단축 및 불필요한 의존성 로드 방지
  • 플러그인 시스템: 확장 가능한 아키텍처 구현
  • 네임스페이스 제어: 깔끔한 API 제공과 내부 구현 세부사항 숨기기
  • 조건부 임포트: 환경에 따른 최적 구현 선택
  • 버전 호환성: 다양한 파이썬 버전에서 작동하는 코드 작성


1️⃣1️⃣ 패키지 테스트 및 품질 관리

테스트 구조

# tests/test_module1.py
import unittest
from mypackage import module1

class TestModule1(unittest.TestCase):
    def test_function1(self):
        result = module1.function1(10)
        self.assertEqual(result, 20)
        
# pytest 사용 예
# tests/test_module2.py
import pytest
from mypackage import module2

def test_function2():
    assert module2.function2(5) == 10
    
# 테스트 실행
# python -m unittest discover
# pytest

품질 관리 도구 설정

# setup.cfg
[flake8]
max-line-length = 88
exclude = .git,__pycache__,build,dist

[mypy]
python_version = 3.8
warn_return_any = True
warn_unused_configs = True

[coverage:run]
source = mypackage
omit = tests/*
# pyproject.toml
[tool.black]
line-length = 88
target-version = ['py38']

[tool.isort]
profile = "black"
line_length = 88

테스트 및 품질 관리 방법:

  • 단위 테스트: unittest, pytest 등을 활용한 모듈별 테스트
  • 통합 테스트: 여러 모듈 간 상호작용 테스트
  • 코드 커버리지: coverage로 테스트 범위 확인
  • 정적 분석: mypy, pyright 등으로 타입 검사
  • 코드 스타일: black, flake8, isort 등으로 일관된 스타일 유지
  • 지속적 통합: GitHub Actions, Travis CI 등으로 자동 테스트 실행


1️⃣2️⃣ 패키지 문서화

Sphinx 문서화 예제

# mypackage/module1.py
def function1(param1, param2=None):
    """
    중요한 기능을 수행하는 함수
    
    Args:
        param1 (int): 첫 번째 매개변수
        param2 (str, optional): 두 번째 매개변수. 기본값은 None
        
    Returns:
        bool: 성공 여부를 반환
        
    Raises:
        ValueError: param1이 0보다 작을 경우
        
    Examples:
        >>> function1(10)
        True
        >>> function1(-5)
        Traceback (most recent call last):
            ...
        ValueError: param1은 양수여야 합니다
    """
    if param1 < 0:
        raise ValueError("param1은 양수여야 합니다")
    # 함수 구현...
    return True

자동 문서 생성 설정

# docs/conf.py
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.viewcode',
    'sphinx.ext.napoleon',
]

# docs/index.rst
.. MyPackage documentation master file

Welcome to MyPackage's documentation!
=====================================

.. toctree::
   :maxdepth: 2
   :caption: Contents:
   
   api
   examples

# docs/api.rst
API Reference
============

.. automodule:: mypackage.module1
   :members:
   :undoc-members:
   :show-inheritance:

효과적인 문서화 방법:

  • 일관된 docstring 형식: Google, NumPy, reStructuredText 중 선택
  • API 문서 자동화: Sphinx, pdoc 등의 도구로 API 문서 자동 생성
  • 튜토리얼과 예제: 실제 사용 사례와 코드 예제 제공
  • 변경 로그: 버전별 변경사항 문서화
  • 온라인 호스팅: Read the Docs, GitHub Pages 등에 문서 배포


패키지 작성 주요 팁

모범 사례:

  • 명확한 패키지 구조 설계: 직관적이고 논리적인 모듈 구조 설계
  • init.py 파일 적절히 활용: 패키지 초기화와 API 정의
  • 순환 참조 피하기: 모듈 간 순환 의존성 제거
  • 상대 경로 임포트 주의: 명시적 상대 경로 사용 및 과도한 중첩 피하기
  • 패키지 문서화: 상세한 docstring과 사용 예제 제공
  • 명확한 버전 관리: 시맨틱 버저닝(SemVer) 준수
  • 하위 호환성 유지: API 변경 시 버전 관리와 문서화
  • 테스트 자동화: CI/CD 파이프라인 구축으로 품질 보장
  • 부가 파일 포함: LICENSE, README, CHANGELOG 등 필수 문서 포함
  • 사용자 피드백 반영: 이슈 트래커 활용 및 커뮤니티 소통


Clone this wiki locally