Akses data dalam subdirektori paket

130

Saya menulis paket python dengan modul yang perlu membuka file data dalam ./data/subdirektori. Saat ini saya memiliki path ke file yang di-hardcode ke dalam kelas dan fungsi saya. Saya ingin menulis kode yang lebih kuat yang dapat mengakses subdirektori di mana pun itu diinstal pada sistem pengguna.

Saya sudah mencoba berbagai metode, tetapi sejauh ini saya tidak beruntung. Tampaknya sebagian besar perintah "direktori saat ini" mengembalikan direktori interpreter python sistem, dan bukan direktori modul.

Sepertinya ini sepele, masalah umum. Namun sepertinya saya tidak bisa memahaminya. Sebagian masalahnya adalah file data saya bukan .pyfile, jadi saya tidak bisa menggunakan fungsi impor dan sejenisnya.

Ada saran?

Sekarang direktori paket saya terlihat seperti:

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

Saya mencoba mengakses data.txtdari module*.py!

Jacob Lyles
sumber

Jawaban:

24

Anda dapat menggunakan __file__untuk mendapatkan path ke paket, seperti ini:

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()
RichieHindle
sumber
44
Ini tidak akan berfungsi jika file berada dalam distribusi (IE. Egg). Gunakan pkg_resources untuk mendapatkan file data.
Chris
2
Memang ini rusak.
Federico
1
Juga, __file__tidak berfungsi dengan py2exe, karena nilainya akan menjadi path ke file zip.
Pod
1
Ini sebenarnya bekerja untuk saya. Tidak punya masalah. Saya menggunakan python 3.6
Jorge
1
Ini tidak akan berfungsi jika distribusi (telur dll).
Adarsh ​​Trivedi
166

Cara standar untuk melakukan ini adalah dengan paket setuptools dan pkg_resources.

Anda dapat mengatur paket Anda sesuai dengan hierarki berikut, dan mengonfigurasi file pengaturan paket untuk mengarahkannya ke sumber daya data Anda, sesuai tautan ini:

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

Anda kemudian dapat menemukan kembali dan menggunakan file-file itu menggunakan pkg_resources, sesuai tautan ini:

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')
elliot42
sumber
7
Tidakkah pkg_resources membuat ketergantungan run-time pada setuptools ? Sebagai contoh, saya mendistribusikan ulang paket Debian jadi mengapa saya python-setuptoolshanya bergantung pada itu? Sejauh ini __file__berfungsi dengan baik untuk saya.
mlt
4
Mengapa ini lebih baik: Kelas ResourceManager menyediakan akses yang seragam ke sumber daya paket, apakah sumber daya itu ada sebagai file dan direktori atau dikompresi dalam arsip sejenis
vrdhn
4
Saran brilian, terima kasih. Saya menerapkan file standar terbuka menggunakanfrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
eageranalyst
5
Bagaimana ini akan bekerja untuk menggunakan paket ketika tidak diinstal? Hanya menguji secara lokal yang saya maksud
Claudiu
11
Dalam python 3.7, importlib.resourcesganti pkg_resourcesuntuk tujuan ini (karena masalah kinerja).
benjimin
13

Untuk memberikan solusi yang berfungsi hari ini. Jelas menggunakan API ini untuk tidak menemukan kembali semua roda itu.

Nama file sistem file yang benar diperlukan. Telur zip akan diekstraksi ke direktori cache:

from pkg_resources import resource_filename, Requirement

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

Mengembalikan objek seperti file yang dapat dibaca untuk sumber daya yang ditentukan; mungkin berupa file aktual, sebuah StringIO, atau beberapa objek serupa. Aliran berada dalam "mode biner", dalam arti bahwa byte apa pun dalam sumber daya akan dibaca apa adanya.

from pkg_resources import resource_stream, Requirement

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

Paket Discovery dan Akses Sumberdaya menggunakan pkg_resources

Sascha Gottfried
sumber
10

Seringkali tidak ada gunanya membuat jawaban yang merinci kode yang tidak berfungsi sebagaimana mestinya, tapi saya percaya ini sebagai pengecualian. Python 3.7 menambahkan importlib.resourcesyang seharusnya diganti pkg_resources. Ini akan berfungsi untuk mengakses file di dalam paket yang tidak memiliki garis miring pada namanya, yaitu

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

misalnya Anda dapat mengakses data2.txtpaket dalam foodengan misalnya

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

tetapi akan gagal dengan pengecualian untuk

>>> 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

Hal ini tidak bisa diperbaiki kecuali dengan menempatkan __init__.pydi datadan kemudian menggunakannya sebagai sebuah paket:

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

Alasan perilaku ini adalah "itu karena desain" ; tetapi desain mungkin berubah ...

Antti Haapala
sumber
Apakah Anda memiliki tautan yang lebih baik untuk "itu sesuai desain" daripada video youtube - lebih disukai yang berisi teks?
gerrit
@gerrit yang kedua berisi teks. "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

Anda memerlukan nama untuk seluruh modul Anda, Anda diberikan pohon direktori tidak mencantumkan detail itu, bagi saya ini bekerja:

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

Terlihat setuptools tampaknya tidak menyelesaikan file berdasarkan nama yang cocok dengan file data yang dikemas, jadi Anda harus memasukkan data/awalan cukup banyak tidak peduli apa. Anda dapat menggunakan os.path.join('data', 'data.txt)jika Anda memerlukan pemisah direktori alternatif, Umumnya saya tidak menemukan masalah kompatibilitas dengan pemisah direktori unix style hard-coded sekalipun.

ThorSummoner
sumber
docs.python.org/3.6/distutils/… > Perhatikan bahwa setiap nama path (file atau direktori) yang disediakan dalam skrip setup harus ditulis menggunakan konvensi Unix, yaitu dipisahkan dengan slash. Distutils akan mengatur konversi representasi netral-platform ini menjadi apa pun yang sesuai pada platform Anda saat ini sebelum benar-benar menggunakan pathname. Ini membuat skrip pengaturan Anda portabel di seluruh sistem operasi, yang tentu saja merupakan salah satu tujuan utama Distutils. Dalam semangat ini, semua nama path dalam dokumen ini dipisahkan dengan garis miring.
changyuheng
6

Saya pikir saya mencari jawaban.

Saya membuat modul data_path.py, yang saya impor ke modul saya yang lain yang berisi:

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

Dan kemudian saya membuka semua file saya dengan

open(os.path.join(data_path,'filename'), <param>)
Jacob Lyles
sumber
2
Ini akan gagal berfungsi ketika sumber daya ada dalam distribusi arsip (seperti telur zip). Lebih suka sesuatu seperti itu:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis
@ankostis setuptools cukup pintar untuk mengekstrak arsip jika mendeteksi bahwa Anda menggunakan __file__suatu tempat. Dalam kasus saya, saya menggunakan perpustakaan yang benar-benar menginginkan jalur dan bukan stream. Tentu saja saya bisa menulis file sementara ke disk tetapi malas saya hanya menggunakan fitur setuptools.
letmaik