Python 패키지에 추가 파일 포함하는 방법

PythonBeginner
지금 연습하기

소개

Python 패키지는 코드를 구성하고 배포하는 강력한 방법입니다. Python 스크립트 (.py 파일) 가 패키지의 핵심을 형성하는 반면, 구성 파일, 데이터 파일, 템플릿 또는 문서와 같은 추가 파일을 포함해야 하는 경우가 많습니다. 이 튜토리얼에서는 이러한 추가 리소스를 포함하는 Python 패키지를 생성하는 과정을 안내하여 패키지를 더욱 다재다능하고 유용하게 만들 것입니다.

이 Lab 이 끝나면 추가 파일이 포함된 완전한 Python 패키지를 생성하고 코드에서 이러한 파일에 액세스하는 방법을 배우게 됩니다.

기본 Python 패키지 구조 생성

기본 Python 패키지 구조를 생성하는 것으로 시작해 보겠습니다. 패키지는 본질적으로 Python 모듈과 Python 에 이 디렉토리를 패키지로 취급해야 한다고 알려주는 특수한 __init__.py 파일을 포함하는 디렉토리입니다.

패키지 디렉토리 구조 생성

먼저, 패키지에 필요한 디렉토리를 생성해 보겠습니다.

mkdir -p ~/project/mypackage/data

이 명령은 추가 파일을 저장하기 위한 하위 디렉토리 data가 있는 mypackage라는 디렉토리를 생성합니다.

이제 프로젝트 디렉토리로 이동해 보겠습니다.

cd ~/project

기본 패키지 파일 생성

모든 Python 패키지에는 루트 디렉토리에 __init__.py 파일이 필요합니다. 이 파일을 생성해 보겠습니다.

touch mypackage/__init__.py

이 빈 파일은 Python 에 mypackage 디렉토리가 패키지임을 알려줍니다.

다음으로, 패키지 내에 간단한 Python 모듈을 생성해 보겠습니다.

echo 'def greet():
    print("Hello from mypackage!")' > mypackage/greeting.py

패키지에 데이터 파일 추가

이제 패키지에 데이터 파일을 추가해 보겠습니다. 이는 구성 파일, CSV 파일 또는 패키지에 필요한 다른 유형의 파일일 수 있습니다.

echo 'This is sample data for our package.' > mypackage/data/sample.txt

구성 파일도 생성해 보겠습니다.

echo '[config]
debug = true
log_level = INFO' > mypackage/config.ini

패키지 구조 확인

다음 명령으로 패키지 구조를 확인할 수 있습니다.

find mypackage -type f | sort

다음과 유사한 출력을 볼 수 있습니다.

mypackage/__init__.py
mypackage/config.ini
mypackage/data/sample.txt
mypackage/greeting.py

이것은 일부 추가 비 Python 파일이 있는 기본 Python 패키지 구조입니다. 다음 단계에서는 패키지를 배포할 때 이러한 파일을 포함하는 방법과 코드에서 액세스하는 방법을 배우게 됩니다.

패키지 설정을 위한 스크립트 생성

Python 패키지에 추가 파일을 제대로 포함하려면 setup.py 파일을 생성해야 합니다. 이 파일은 Python 의 패키징 도구에서 패키지를 빌드하고 설치하는 데 사용됩니다.

setup.py 이해

setup.py 파일에는 이름, 버전, 작성자 및 종속성과 같은 패키지에 대한 메타데이터가 포함되어 있습니다. 또한 패키지를 배포할 때 포함해야 하는 파일도 지정합니다.

프로젝트의 루트 디렉토리에 기본 setup.py 파일을 생성해 보겠습니다.

cd ~/project

이제 다음 내용으로 setup.py 파일을 생성합니다.

cat > setup.py << 'EOF'
from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="0.1",
    packages=find_packages(),
    
    ## Include data files
    package_data={
        "mypackage": ["config.ini", "data/*.txt"],
    },
    
    ## Metadata
    author="Your Name",
    author_email="your.email@example.com",
    description="A simple Python package with additional files",
)
EOF

패키지 데이터 구성 이해

package_data 매개변수는 패키지에 추가 파일을 포함하는 데 핵심입니다. 다음으로 구성된 딕셔너리를 사용합니다.

  • 키는 패키지 이름입니다 (또는 모든 패키지에 대해 "" 사용).
  • 값은 패키지 디렉토리에 상대적인 파일 패턴 목록입니다.

이 예제에서는 다음을 포함하고 있습니다.

  • 패키지 루트에 있는 config.ini 파일
  • data 디렉토리의 모든 .txt 파일

파일 패턴은 유사한 이름 또는 확장자를 가진 여러 파일을 일치시키기 위해 *와 같은 와일드카드를 지원합니다.

설정 구성 테스트

패키지를 테스트하기 위해 가상 환경을 생성해 보겠습니다.

python3 -m venv ~/project/venv
source ~/project/venv/bin/activate

이제 개발 모드로 패키지를 설치해 보겠습니다.

cd ~/project
pip install -e .

-e 플래그는 "편집 가능 (editable)" 모드를 나타내며, 이는 패키지 코드를 매번 다시 설치하지 않고도 편집할 수 있음을 의미합니다.

패키지가 성공적으로 설치되었음을 나타내는 출력을 볼 수 있습니다.

Successfully installed mypackage-0.1

패키지 설치를 확인해 보겠습니다.

python -c "import mypackage.greeting; mypackage.greeting.greet()"

다음과 같은 출력이 표시되어야 합니다.

Hello from mypackage!

이제 추가 파일이 포함된 설정 스크립트가 있는 Python 패키지를 성공적으로 생성했습니다. 다음 단계에서는 Python 코드에서 이러한 파일에 액세스하는 방법을 배우게 됩니다.

패키지 내 추가 파일에 액세스하기

이제 패키지에 추가 파일을 포함했으므로 Python 코드에서 해당 파일에 액세스하는 방법을 배워야 합니다. 여러 가지 방법이 있지만 가장 신뢰할 수 있는 방법은 setuptools 패키지의 pkg_resources 모듈을 사용하는 것입니다.

추가 파일에 액세스하기 위한 모듈 생성

추가 파일에 액세스하는 방법을 보여주는 패키지에 새 모듈을 생성해 보겠습니다.

cd ~/project

mypackage 디렉토리에 fileaccess.py라는 새 파일을 생성합니다.

cat > mypackage/fileaccess.py << 'EOF'
import os
import pkg_resources

def get_config_path():
    """Return the path to the config.ini file."""
    return pkg_resources.resource_filename('mypackage', 'config.ini')

def read_config():
    """Read and return the content of the config.ini file."""
    config_path = get_config_path()
    with open(config_path, 'r') as f:
        return f.read()

def get_sample_data_path():
    """Return the path to the sample.txt file."""
    return pkg_resources.resource_filename('mypackage', 'data/sample.txt')

def read_sample_data():
    """Read and return the content of the sample.txt file."""
    data_path = get_sample_data_path()
    with open(data_path, 'r') as f:
        return f.read()

def list_package_data():
    """List all files included in the package data."""
    ## Get the package directory
    package_dir = os.path.dirname(pkg_resources.resource_filename('mypackage', '__init__.py'))
    
    ## List files in the main package directory
    main_files = [f for f in os.listdir(package_dir) 
                  if os.path.isfile(os.path.join(package_dir, f))]
    
    ## List files in the data directory
    data_dir = os.path.join(package_dir, 'data')
    data_files = [f'data/{f}' for f in os.listdir(data_dir) 
                 if os.path.isfile(os.path.join(data_dir, f))]
    
    return main_files + data_files
EOF

__init__.py 파일 업데이트

새로운 함수를 노출하도록 __init__.py 파일을 업데이트해 보겠습니다.

cat > mypackage/__init__.py << 'EOF'
from mypackage.greeting import greet
from mypackage.fileaccess import (
    get_config_path,
    read_config,
    get_sample_data_path,
    read_sample_data,
    list_package_data
)

__all__ = [
    'greet',
    'get_config_path',
    'read_config',
    'get_sample_data_path',
    'read_sample_data',
    'list_package_data'
]
EOF

파일 액세스 함수 테스트

파일 액세스 함수를 테스트하는 스크립트를 생성해 보겠습니다.

cat > ~/project/test_package.py << 'EOF'
import mypackage

## Test greeting function
print("Testing greeting function:")
mypackage.greet()
print()

## Test config file access
print("Config file path:")
print(mypackage.get_config_path())
print("\nConfig file content:")
print(mypackage.read_config())
print()

## Test data file access
print("Sample data file path:")
print(mypackage.get_sample_data_path())
print("\nSample data file content:")
print(mypackage.read_sample_data())
print()

## List all package data
print("All package data files:")
for file in mypackage.list_package_data():
    print(f"- {file}")
EOF

이제 테스트 스크립트를 실행합니다.

cd ~/project
python test_package.py

다음과 유사한 출력을 볼 수 있습니다.

Testing greeting function:
Hello from mypackage!

Config file path:
/home/labex/project/mypackage/config.ini

Config file content:
[config]
debug = true
log_level = INFO

Sample data file path:
/home/labex/project/mypackage/data/sample.txt

Sample data file content:
This is sample data for our package.

All package data files:
- __init__.py
- config.ini
- fileaccess.py
- greeting.py
- data/sample.txt

pkg_resources 이해

pkg_resources 모듈은 설치된 패키지 내의 리소스에 액세스하는 방법을 제공합니다. resource_filename 함수는 패키지가 설치된 위치에 관계없이 패키지 내의 파일 경로를 반환합니다.

이 접근 방식은 다음 경우에 코드에서 추가 파일에 액세스할 수 있도록 보장합니다.

  • 개발 중 소스 디렉토리에서 실행
  • 가상 환경에 설치됨
  • 시스템 전체에 설치됨
  • 다른 머신에 배포 및 설치됨

이렇게 하면 패키지가 사용되는 방식에 따라 하드 코딩된 경로 또는 상대 경로에 의존하지 않으므로 패키지의 이식성과 신뢰성이 향상됩니다.

패키지 빌드 및 배포

이제 추가 파일이 있는 Python 패키지를 생성하고 액세스할 수 있는지 확인했으므로 이 패키지를 빌드하고 배포하는 방법을 알아보겠습니다.

설정 스크립트 업데이트

패키지를 빌드하기 전에 setup.py 파일을 업데이트하여 더 많은 메타데이터와 요구 사항을 포함해 보겠습니다.

cd ~/project
cat > setup.py << 'EOF'
from setuptools import setup, find_packages

setup(
    name="mypackage",
    version="0.1.0",
    packages=find_packages(),
    
    ## Include data files
    package_data={
        "mypackage": ["config.ini", "data/*.txt"],
    },
    
    ## Dependencies
    install_requires=[
        "setuptools",
    ],
    
    ## Metadata
    author="Your Name",
    author_email="your.email@example.com",
    description="A simple Python package with additional files",
    keywords="sample, package, data",
    url="https://example.com/mypackage",
    classifiers=[
        "Development Status :: 3 - Alpha",
        "Intended Audience :: Developers",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.8",
        "Programming Language :: Python :: 3.9",
        "Programming Language :: Python :: 3.10",
    ],
    python_requires=">=3.6",
)
EOF

소스 및 휠 배포판 빌드

Python 패키지는 여러 형식으로 배포할 수 있지만 가장 일반적인 형식은 다음과 같습니다.

  1. 소스 배포판 (sdist): 소스 코드 및 추가 파일을 포함하는 tarball
  2. 휠 배포판 (bdist_wheel): 빌드 없이 설치할 수 있는 사전 빌드된 패키지

두 유형의 배포판을 모두 생성해 보겠습니다.

## Make sure we have the latest build tools
pip install --upgrade setuptools wheel

## Build the distributions
python setup.py sdist bdist_wheel

배포판이 생성되었음을 나타내는 출력이 표시되고 dist 디렉토리에 새 파일이 나타납니다.

dist 디렉토리의 내용을 확인해 보겠습니다.

ls -l dist

다음과 같은 두 개 이상의 파일이 표시됩니다.

  • .tar.gz 파일 (소스 배포판)
  • .whl 파일 (휠 배포판)

배포 파일에서 패키지 설치

이제 배포 파일 중 하나에서 패키지를 설치하는 것을 테스트해 보겠습니다. 먼저 개발 버전을 제거해 보겠습니다.

pip uninstall -y mypackage

이제 휠 배포판을 설치해 보겠습니다.

pip install dist/mypackage-0.1.0-py3-none-any.whl

패키지가 성공적으로 설치되었음을 나타내는 출력이 표시됩니다.

패키지가 설치되었는지, 그리고 여전히 추가 파일에 액세스할 수 있는지 확인해 보겠습니다.

python -c "import mypackage; print(mypackage.read_config())"

이것은 config.ini 파일의 내용을 출력해야 합니다.

[config]
debug = true
log_level = INFO

패키지 게시

실제 시나리오에서는 일반적으로 패키지를 Python Package Index (PyPI) 에 게시하여 다른 사용자가 pip install mypackage를 사용하여 설치할 수 있도록 합니다. 여기에는 다음이 포함됩니다.

  1. PyPI (https://pypi.org/) 에서 계정 생성

  2. twine과 같은 도구를 사용하여 배포판 업로드:

    pip install twine
    twine upload dist/*

그러나 이 랩에서는 로컬에서 배포판을 생성하는 것으로 마무리합니다. 이제 다른 사용자가 배포하고 설치할 수 있는 추가 파일이 있는 완전한 Python 패키지가 있습니다.

생성한 내용 요약

  • 모듈 및 추가 파일이 있는 Python 패키지
  • 배포판에 이러한 파일을 포함하는 설정 스크립트
  • 코드에서 이러한 파일에 액세스하는 함수
  • 배포 준비가 된 소스 및 휠 배포 파일

이 구조는 향후 생성하려는 모든 Python 패키지에 대한 견고한 기반을 제공합니다.

요약

이 랩에서는 다음을 배우셨습니다.

  1. Python 이 아닌 추가 파일이 있는 기본 Python 패키지 구조를 생성합니다.
  2. 패키지 배포에 이러한 파일을 포함하도록 setup.py를 구성합니다.
  3. pkg_resources 모듈을 사용하여 Python 코드에서 추가 파일에 액세스합니다.
  4. 배포를 위해 패키지의 소스 및 휠 배포판을 빌드합니다.

이제 Python 코드뿐만 아니라 구성 파일, 데이터 파일, 템플릿 및 기타 리소스를 포함하는 보다 포괄적인 Python 패키지를 만들 수 있는 지식을 갖추게 되었습니다. 이 기능은 Python 코드가 종종 외부 파일과 함께 작동해야 하는 실제 애플리케이션을 개발하는 데 필수적입니다.

이 랩의 몇 가지 주요 내용:

  • setup()에서 package_data 매개변수를 사용하여 추가 파일을 포함합니다.
  • pkg_resources.resource_filename()을 사용하여 코드에서 이러한 파일에 안정적으로 액세스합니다.
  • 최대 호환성을 위해 소스 및 휠 배포판을 모두 빌드합니다.
  • 유지 관리를 더 쉽게 하기 위해 패키지 구조를 정리합니다.

이 지식은 더 복잡한 Python 애플리케이션 및 패키지를 계속 개발하는 데 유용할 것입니다.