回答:
[2016年6月15日追加:明らかにこれはすべての状況で機能するわけではありません。他の回答を参照してください]
import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')
importlib.resources
モジュールを使用します。伝統 pkg_resources
からsetuptools
もはや推奨されていない新しい方法理由は次のとおりです。
setuptools
)を必要としないため、開発時には高速ですが、Pythonの標準ライブラリのみに依存します。既存のコードを移植するときの新しい方法との違いを説明するために、最初にリストした従来の方法をそのまま使用しました(移植もここで説明されています)。
テンプレートがモジュールのパッケージ内にネストされたフォルダーにあるとしましょう:
<your-package>
+--<module-asking-the-file>
+--templates/
+--temp_file <-- We want this file.
注1:確かに、
__file__
属性をいじらないでください(たとえば、zipから提供されるとコードが壊れます)。注2:このパッケージをビルドする場合は、データファイルを内
package_data
またはdata_files
内で宣言することを忘れないでくださいsetup.py
。
pkg_resources
from setuptools
(遅い)setuptoolsディストリビューションのpkg_resources
パッケージを使用することもできますが、それにはパフォーマンス面でのコストが伴います。
import pkg_resources
# Could be any dot-separated package/module name or a "Requirement"
resource_package = __name__
resource_path = '/'.join(('templates', 'temp_file')) # Do not use os.path.join()
template = pkg_resources.resource_string(resource_package, resource_path)
# or for a file-like stream:
template = pkg_resources.resource_stream(resource_package, resource_path)
チップ:
これは、あなたが設定することができて、あなたのディストリビューションは、zip形式の場合でも、データを読み込みます
zip_safe=True
あなたの中にsetup.py
、および/または待望の使用zipapp
パッカーをからのpython-3.5自己完結型の分布を作成します。
setuptools
ランタイム要件に必ず追加してください(たとえば、install_requires`に)。
...そしてSetuptools / pkg_resources
docsによると、あなたは使用すべきではないことに注意してくださいos.path.join
:
基本的なリソースアクセス
リソース名は
/
-pathで区切られている必要があり、絶対パス(つまり、先頭に/
)を付けたり、 "..
"のような相対名を含めることはできません。リソースパスはファイルシステムパスではないため、ルーチンを使用してリソースパスを操作しないでください。os.path
importlib_resources
ライブラリの使用上記よりも効率的な標準ライブラリのimportlib.resources
モジュールを使用しますsetuptools
。
try:
import importlib.resources as pkg_resources
except ImportError:
# Try backported to PY<37 `importlib_resources`.
import importlib_resources as pkg_resources
from . import templates # relative-import the *package* containing the templates
template = pkg_resources.read_text(templates, 'temp_file')
# or for a file-like stream:
template = pkg_resources.open_text(templates, 'temp_file')
注意:
機能について
read_text(package, resource)
:
package
文字列またはモジュールのいずれかになります。- これ
resource
はもはやパスではなく、既存のパッケージ内で開くリソースのファイル名だけです。パスの区切り文字が含まれていない可能性があり、サブリソースがない可能性があります(つまり、ディレクトリにすることはできません)。
質問の例では、次のようにする必要があります。
<your_package>/templates/
空作成することによって、適切なパッケージに__init__.py
、その中にファイルを、import
ステートメントを使用できます(パッケージ/モジュール名の解析は不要になります)。resource_name = "temp_file"
(パスなし)を要求します。チップ:
- 現在のモジュール内のファイルにアクセスするには、パッケージの引数をに設定します(
__package__
たとえばpkg_resources.read_text(__package__, 'temp_file')
、@ ben-maresに感謝します)。- 実際にファイル名がで尋ねられると
path()
、一時的に作成されたファイルにコンテキストマネージャが使用されるようになるため、状況は面白くなります(これを読んでください)。- 条件付きで古いPythonのバックポートされたライブラリを追加します(プロジェクトをでパッケージする場合はこれを
install_requires=[" importlib_resources ; python_version<'3.7'"]
チェックしてください)。setuptools<36.2.1
- 従来の方法から移行した場合は、ランタイム要件
setuptools
からライブラリを削除することを忘れないでください。- カスタマイズする
setup.py
か、静的ファイルMANIFEST
を含めることを忘れないでください。- で設定することもでき
zip_safe=True
ますsetup.py
。
NotImplementedError: Can't perform this operation for loaders without 'get_data()'
任意のアイデア?
importlib.resources
とpkg_resources
は必ずしも互換性がないことに注意してください。importlib.resources
に追加されたzipファイルsys.path
、setuptools、およびpkg_resources
それ自体がに追加されたディレクトリに格納されたzipファイルであるeggファイルを処理しますsys.path
。例:sys.path = [..., '.../foo', '.../bar.zip']
、卵はに入り.../foo
ますが、のパッケージbar.zip
もインポートできます。を使用pkg_resources
して、のパッケージからデータを抽出することはできませんbar.zip
。setuptools importlib.resources
がegg を操作するために必要なローダーを登録しているかどうかは確認していません。
Package has no location
が表示された場合、追加のsetup.py構成が必要ですか?
templates
例のようにサブモジュールではなく)、package
引数を__package__
に設定できます。例:pkg_resources.read_text(__package__, 'temp_file')
リソースファイルの読み取りについて心配する前に、最初のステップは、最初にデータファイルがディストリビューションにパッケージ化されていることを確認することです。ソースツリーから直接読み取るのは簡単ですが、重要な部分はこれらのリソースファイルに、インストールされたパッケージ内のコードからアクセスできることを確認してください。
プロジェクトを次のように構成し、データファイルをパッケージ内のサブディレクトリに配置します。
.
├── package
│ ├── __init__.py
│ ├── templates
│ │ └── temp_file
│ ├── mymodule1.py
│ └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py
あなたは渡す必要include_package_data=True
でsetup()
コール。マニフェストファイルは、setuptools / distutilsを使用してソース配布をビルドする場合にのみ必要です。templates/temp_file
がこのサンプルプロジェクト構造に確実にパッケージ化されるようにするには、次のような行をマニフェストファイルに追加します。
recursive-include package *
過去の注意点: flit、poetryなどの最新のビルドバックエンドでは、マニフェストファイルを使用する必要はありません。これには、デフォルトでパッケージデータファイルが含まれます。したがって、使用pyproject.toml
していてsetup.py
ファイルがない場合は、に関するすべてのものを無視できますMANIFEST.in
。
さて、パッケージを邪魔にならないようにして、読み取り部分に...
標準ライブラリpkgutil
APIを使用します。ライブラリコードでは次のようになります。
# within package/mymodule1.py, for example
import pkgutil
data = pkgutil.get_data(__name__, "templates/temp_file")
print("data:", repr(data))
text = pkgutil.get_data(__name__, "templates/temp_file").decode()
print("text:", repr(text))
ジップで動作します。Python 2とPython 3で動作します。サードパーティの依存関係は必要ありません。私はどんなマイナス面も実際には知りません(もしそうなら、答えにコメントしてください)。
これは現在受け入れられている答えです。よくても、次のようになります。
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))
それの何がいけないの?利用可能なファイルとサブディレクトリがあるという仮定は正しくありません。このアプローチは、zipまたはwheelにパックされたコードを実行する場合は機能せず、パッケージがファイルシステムに抽出されるかどうかにかかわらず、完全にユーザーの制御の及ばない可能性があります。
これは、トップ投票の回答で説明されています。次のようになります。
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
それの何がいけないの?実行時の依存関係をsetuptoolsに追加します。これは、インストール時の依存関係のみであることが望ましいです。自分のパッケージリソースにのみ関心があったpkg_resources
としても、コードはインストールされたすべてのパッケージのワーキングセットを構築するため、インポートと使用は非常に遅くなる可能性があります。これは、インストール時には(インストールは1回限りなので)大したことではありませんが、実行時には醜いです。
これは現在、トップ投票の回答の推奨事項です。これは最近の標準ライブラリの追加(Python 3.7の新機能)ですが、利用可能なバックポートもあります。次のようになります。
try:
from importlib.resources import read_binary
from importlib.resources import read_text
except ImportError:
# Python 2.x backport
from importlib_resources import read_binary
from importlib_resources import read_text
data = read_binary("package.templates", "temp_file")
print("data", repr(data))
text = read_text("package.templates", "temp_file")
print("text", repr(text))
それの何がいけないの?まあ、残念ながら、それは動作しません...まだ。これはまだ不完全なAPI importlib.resources
です。templates/__init__.py
データファイルがサブディレクトリではなくサブパッケージ内に存在するようにするには、空のファイルを追加する必要があります。また、package/templates
サブディレクトリをインポート可能なpackage.templates
サブパッケージとして公開します。それが大した問題ではなく、気にならない場合は、先に進んで__init__.py
そこにファイルを追加し、インポートシステムを使用してリソースにアクセスできます。ただし、そのときは、my_resources.py
代わりにファイルにして、モジュールでいくつかのバイト変数または文字列変数を定義してから、Pythonコードにインポートすることもできます。これは、どちらにせよここでの重労働を行うインポートシステムです。
私はgithubでサンプルプロジェクトを作成し、PyPIにアップロードしました。これは、上記の4つのアプローチすべてを示しています。それを試してみてください:
$ pip install resources-example
$ resources-example
詳細については、https://github.com/wimglenn/resources-exampleを参照してください。
pkgutil.get_data()
確認した私の直感で-それが-廃止されるAPI、発展途上です。そうは言っても、私はあなたに同意しますimportlib.resources
が、はるかに優れた代替手段ではありませんが、PY3.10がこれを解決するまで、私はこの選択を支持します。
pkgutil
廃止されたバッテリーを標準ライブラリーから削除することの非推奨スケジュールではまったく言及されておらず、正当な理由なしに削除されることはほとんどありません。Python 2.3以降で使用されており、PEP 302のローダープロトコルの一部として指定されています。「十分に定義されていないAPI」の使用は、Pythonの標準ライブラリの大部分を説明できるほど説得力のある回答ではありません。
あなたがこの構造を持っている場合
lidtk
├── bin
│ └── lidtk
├── lidtk
│ ├── analysis
│ │ ├── char_distribution.py
│ │ └── create_cm.py
│ ├── classifiers
│ │ ├── char_dist_metric_train_test.py
│ │ ├── char_features.py
│ │ ├── cld2
│ │ │ ├── cld2_preds.txt
│ │ │ └── cld2wili.py
│ │ ├── get_cld2.py
│ │ ├── text_cat
│ │ │ ├── __init__.py
│ │ │ ├── README.md <---------- say you want to get this
│ │ │ └── textcat_ngram.py
│ │ └── tfidf_features.py
│ ├── data
│ │ ├── __init__.py
│ │ ├── create_ml_dataset.py
│ │ ├── download_documents.py
│ │ ├── language_utils.py
│ │ ├── pickle_to_txt.py
│ │ └── wili.py
│ ├── __init__.py
│ ├── get_predictions.py
│ ├── languages.csv
│ └── utils.py
├── README.md
├── setup.cfg
└── setup.py
このコードが必要です:
import pkg_resources
# __name__ in case you're within the package
# - otherwise it would be 'lidtk' in this example as it is the package name
path = 'classifiers/text_cat/README.md' # always use slash
filepath = pkg_resources.resource_filename(__name__, path)
変な「常にスラッシュを使用する」部分はsetuptools
APIに由来します
また、Windowsを使用している場合でも、パスを使用する場合は、パスの区切り文字としてスラッシュ(/)を使用する必要があることに注意してください。Setuptoolsは、ビルド時にスラッシュを適切なプラットフォーム固有のセパレーターに自動的に変換します
ドキュメントがどこにあるのか疑問に思う場合:
答えを提供するDavid BeazleyおよびBrian K. JonesによるPython Cookbook、Third Editionの「10.8。パッケージ内のデータファイルの読み取り」の内容。
私はここにそれを取得します:
次のように編成されたファイルを含むパッケージがあるとします。
mypackage/
__init__.py
somedata.dat
spam.py
次に、spam.pyファイルがsomedata.datファイルの内容を読み取りたいと仮定します。これを行うには、次のコードを使用します。
import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')
結果の変数データは、ファイルの生の内容を含むバイト文字列になります。
get_data()の最初の引数は、パッケージ名を含む文字列です。直接指定するか、などの特殊変数を使用できます__package__
。2番目の引数は、パッケージ内のファイルの相対名です。最終的なディレクトリがパッケージ内にある限り、必要に応じて、標準のUnixファイル名規則を使用して別のディレクトリに移動できます。
このように、パッケージはディレクトリ、.zipまたは.eggとしてインストールできます。
パッケージ内のすべてのpythonモジュールには、 __file__
属性ます
次のように使用できます。
import os
from mypackage
templates_dir = os.path.join(os.path.dirname(mypackage.__file__), 'templates')
template_file = os.path.join(templates_dir, 'template.txt')
卵のリソースについては、http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resourcesをご覧ください。
eggファイルを使用していると仮定します。抽出されません:
私は最近のプロジェクトでこれを「解決」しました。postinstallスクリプトを使用して、テンプレートをegg(zipファイル)からファイルシステムの適切なディレクトリに抽出します。これは、__path__[0]
ことは時々失敗可能でした(名前を思い出せませんが、少なくとも1つのライブラリを調べて、リストの前に何かを追加しました!)。
また、卵ファイルは通常、「卵キャッシュ」と呼ばれる一時的な場所にその場で抽出されます。スクリプトを開始する前または後で、環境変数を使用してその場所を変更できます。
os.environ['PYTHON_EGG_CACHE'] = path
ただし、正しく機能する可能性のあるpkg_resourcesがあります。