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

この空のファイルは、mypackage ディレクトリがパッケージであることを Python に伝えます。

次に、パッケージ内にシンプルな 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

ソースおよび Wheel ディストリビューションのビルド

Python パッケージはいくつかの形式で配布できますが、最も一般的なのは次のとおりです。

  1. ソースディストリビューション (sdist): ソースコードと追加ファイルを含む tarball
  2. Wheel ディストリビューション (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

少なくとも 2 つのファイルが表示されるはずです。

  • .tar.gz ファイル (ソースディストリビューション)
  • .whl ファイル (wheel ディストリビューション)

ディストリビューションファイルからのパッケージのインストール

次に、ディストリビューションファイルの 1 つからパッケージをインストールするテストを行いましょう。まず、開発バージョンをアンインストールしましょう。

pip uninstall -y mypackage

次に、wheel ディストリビューションをインストールしましょう。

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 パッケージ
  • ディストリビューションにこれらのファイルを含めるセットアップスクリプト
  • コードからこれらのファイルにアクセスするための関数
  • 配布の準備ができたソースおよび wheel ディストリビューションファイル

この構造は、将来作成する可能性のあるすべての Python パッケージの強固な基盤を提供します。

まとめ

この実験では、以下の方法を学びました。

  1. Python 以外の追加ファイルを含む基本的な Python パッケージ構造を作成する
  2. パッケージ配布にこれらのファイルを含めるように setup.py を設定する
  3. pkg_resources モジュールを使用して、Python コードから追加ファイルにアクセスする
  4. 配布用にパッケージのソースおよび wheel ディストリビューションをビルドする

これで、Python コードだけでなく、設定ファイル、データファイル、テンプレート、その他のリソースも含む、より包括的な Python パッケージを作成するための知識が得られました。この機能は、Python コードが外部ファイルと連携する必要がある実際のアプリケーションを開発するために不可欠です。

この実験からの主なポイントは次のとおりです。

  • setup()package_data パラメータを使用して追加ファイルを含める
  • pkg_resources.resource_filename() を使用して、コードからこれらのファイルに確実にアクセスする
  • 最大限の互換性のために、ソースと wheel の両方のディストリビューションをビルドする
  • メンテナンスを容易にするために、パッケージ構造を整理する

この知識は、より複雑な Python アプリケーションとパッケージを開発し続ける際に役立ちます。