パッケージサブディレクトリのデータにアクセスする


130

./data/サブディレクトリのデータファイルを開く必要があるモジュールを含むpythonパッケージを書いています。現在、クラスと関数にハードコードされたファイルへのパスがあります。ユーザーのシステムのどこにインストールされているかに関係なく、サブディレクトリにアクセスできるより堅牢なコードを書きたいと思います。

いろいろな方法を試しましたが、今のところ運がありません。ほとんどの「現在のディレクトリ」コマンドは、モジュールのディレクトリではなく、システムのpythonインタプリタのディレクトリを返すようです。

これは、ささいな一般的な問題であるはずです。しかし、私はそれを理解することができないようです。問題の一部は、データファイルがファイルではない.pyため、インポート機能などを使用できないことです。

助言がありますか?

現在、パッケージディレクトリは次のようになっています。

/
__init__.py
module1.py
module2.py
data/   
   data.txt

私がアクセスしようとしていますdata.txtからmodule*.py


回答:


24

次の__file__ようにして、パッケージへのパスを取得できます。

import os
this_dir, this_filename = os.path.split(__file__)
DATA_PATH = os.path.join(this_dir, "data", "data.txt")
print open(DATA_PATH).read()

44
ファイルがディストリビューション(IE。egg)にある場合、これは機能しません。pkg_resourcesを使用して、データファイルを取得します。
クリス

2
確かに、これは壊れています。
フェデリコ

1
また、__file__値はzipファイルのパスになるため、py2exeでは機能しません。
ポッド

1
これは実際に私のために働いた。問題ありませんでした。私はPython 3.6を使用しています
Jorge

1
これは、配布(卵など)の場合は機能しません。
Adarsh Trivedi

166

これを行う標準的な方法は、setuptoolsパッケージとpkg_resourcesを使用することです。

このリンクに従って、次の階層に従ってパッケージをレイアウトし、パッケージリソースをポイントするようにパッケージセットアップファイルを構成できます。

http://docs.python.org/distutils/setupscript.html#installing-package-data

このリンクに従って、pkg_resourcesを使用してそれらのファイルを再検索して使用できます。

http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access

import pkg_resources

DATA_PATH = pkg_resources.resource_filename('<package name>', 'data/')
DB_FILE = pkg_resources.resource_filename('<package name>', 'data/sqlite.db')

7
pkg_resourcesはsetuptoolsにランタイム依存関係を作成しませんか?たとえば、Debianパッケージを再配布しているのに、なぜそれだけに依存しpython-setuptoolsているのですか?これまでのところ__file__、私にとってはうまくいきます。
mlt 2013

4
これが優れている理由:ResourceManagerクラスは、パッケージリソースへの均一なアクセスを提供します。これらのリソースがファイルおよびディレクトリとして存在するか、何らかのアーカイブで圧縮されているかに
関係なく

4
素晴らしい提案、ありがとう。を使用して開いている標準ファイルを実装しましたfrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
熱心なアナリスト2014

5
インストールされていないパッケージを使用する場合、これはどのように機能しますか?ただ、ローカルでテスト私が意味する
クラウディウ

11
Python 3.7では、この目的のためにimportlib.resources置き換えpkg_resourcesられます(パフォーマンスの問題のため)。
ベンジミン

13

今日機能するソリューションを提供するため。これらのホイールをすべて再発明しないように、このAPIを確実に使用してください。

真のファイルシステムのファイル名が必要です。圧縮された卵はキャッシュディレクトリに抽出されます。

from pkg_resources import resource_filename, Requirement

path_to_vik_logo = resource_filename(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

指定されたリソースの読み取り可能なファイルのようなオブジェクトを返します。実際のファイル、StringIO、または同様のオブジェクトの場合があります。ストリームは「バイナリモード」です。つまり、リソース内のバイトはそのまま読み取られます。

from pkg_resources import resource_stream, Requirement

vik_logo_as_stream = resource_stream(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

pkg_resourcesを使用したパッケージ検出とリソースアクセス


10

多くの場合、詳細コードがそのままでは機能しないという答えを出すのは意味がありませが、これは例外であると思います。importlib.resources置き換えpkg_resourcesられることになっているPython 3.7が追加されました。名前にスラッシュが含まれていないパッケージ内のファイルにアクセスする場合に機能します。

foo/
    __init__.py
    module1.py
    module2.py
    data/   
       data.txt
    data2.txt

つまり、たとえば次のようにしdata2.txtてパッケージ内にアクセスできますfoo

importlib.resources.open_binary('foo', 'data2.txt')

しかし、それは例外で失敗します

>>> importlib.resources.open_binary('foo', 'data/data.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/importlib/resources.py", line 87, in open_binary
    resource = _normalize_path(resource)
  File "/usr/lib/python3.7/importlib/resources.py", line 61, in _normalize_path
    raise ValueError('{!r} must be only a file name'.format(path))
ValueError: 'data/data2.txt' must be only a file name

これを配置__init__.pydataてパッケージとして使用しない限り、これを修正することはできません。

importlib.resources.open_binary('foo.data', 'data.txt')

この動作の理由は、「仕様によるもの」です。しかし、デザインが変わる可能性があります ...


YouTubeのビデオよりも「それは設計による」というより良いリンクがありますか。できればテキスト付きのリンクがありますか?
gerrit

@gerrit 2番目にはテキストが含まれます。"This was a deliberate choice, but I think you have a valid use case. @brettcannon what do you think? And if we allow this, should we make sure it gets into Python 3.7?"
Antti Haapala

8

モジュール全体の名前が必要です。ディレクトリツリーにはその詳細がリストされていません。私にとってこれはうまくいきました:

import pkg_resources
print(    
    pkg_resources.resource_filename(__name__, 'data/data.txt')
)

注目すべきことに、setuptoolsはパックされたデータファイルとの名前の一致に基づいてファイルを解決するようには見えないので、data/何があってもプレフィックスをほとんど含める必要があります。os.path.join('data', 'data.txt)代替のディレクトリセパレータが必要な場合に使用できます。ただし、ハードコードされたUNIXスタイルのディレクトリセパレータとの互換性の問題は通常ありません。


docs.python.org/3.6/distutils/… >セットアップスクリプトで提供されるパス名(ファイルまたはディレクトリ)は、Unixの規則を使用して、つまりスラッシュで区切って記述してください。Distutilsは、実際にパス名を使用する前に、このプラットフォームに依存しない表現を現在のプラットフォームで適切なものに変換します。これにより、オペレーティングシステム間でセットアップスクリプトを移植できるようになります。もちろん、これはDistutilsの主要な目標の1つです。この精神で、このドキュメントのすべてのパス名はスラッシュで区切られています。
changyuheng

6

私は答えを見つけたと思います。

モジュールdata_path.pyを作成します。これを含む他のモジュールにインポートします。

data_path = os.path.join(os.path.dirname(__file__),'data')

そして、私はすべてのファイルを

open(os.path.join(data_path,'filename'), <param>)

2
リソースがアーカイブ配布(zip形式の卵など)の場合、これは機能しません。そのようなものを好む:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis

@ankostis setuptoolsは、__file__どこかで使用したことが検出された場合にアーカイブを抽出するのに十分なほど巧妙です。私の場合、ストリームではなくパスが本当に必要なライブラリを使用します。もちろん、ファイルを一時的にディスクに書き込むこともできますが、怠惰な私はsetuptoolsの機能を使用します。
letmaik 2014
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.