Bisakah Anda memberi tahu saya bagaimana saya bisa membaca file yang ada di dalam paket Python saya?
Situasi saya
Paket yang saya muat memiliki sejumlah template (file teks yang digunakan sebagai string) yang ingin saya muat dari dalam program. Tetapi bagaimana cara menentukan jalur ke file tersebut?
Bayangkan saya ingin membaca file dari:
package\templates\temp_file
Semacam manipulasi jalan? Pelacakan jalur dasar paket?
Jawaban:
[ditambahkan 2016-06-15: tampaknya ini tidak berfungsi di semua situasi. silakan lihat jawaban lainnya]
sumber
TLDR; Gunakan
importlib.resources
modul pustaka standar seperti yang dijelaskan dalam metode no 2, di bawah ini.The tradisional
pkg_resources
darisetuptools
tidak dianjurkan lagi karena metode baru:setuptools
), tetapi hanya mengandalkan pustaka standar Python.Saya menyimpan yang tradisional terlebih dahulu, untuk menjelaskan perbedaan dengan metode baru saat mem-porting kode yang ada (porting juga dijelaskan di sini ).
Mari asumsikan template Anda berada di folder yang bersarang di dalam paket modul Anda:
1) Menggunakan
pkg_resources
darisetuptools
(lambat)Anda dapat menggunakan
pkg_resources
paket dari distribusi setuptools , tetapi itu datang dengan biaya, berdasarkan kinerja :... dan perhatikan bahwa menurut Setuptools /
pkg_resources
docs, Anda tidak boleh menggunakanos.path.join
:2) Python> = 3.7, atau menggunakan
importlib_resources
perpustakaan backportGunakan
importlib.resources
modul perpustakaan standar yang lebih efisien daripada disetuptools
atas:Untuk contoh yang ditanyakan dalam pertanyaan, sekarang kita harus:
<your_package>/templates/
menjadi paket yang tepat, dengan membuat__init__.py
file kosong di dalamnya,import
(tidak perlu lagi mem-parsing nama paket / modul),resource_name = "temp_file"
(tidak ada jalan).sumber
NotImplementedError: Can't perform this operation for loaders without 'get_data()'
ide?importlib.resources
danpkg_resources
yang tidak selalu kompatibel .importlib.resources
berfungsi dengan zipfiles yang ditambahkan kesys.path
, setuptools danpkg_resources
bekerja dengan file telur, yang merupakan file zip yang disimpan dalam direktori yang ditambahkan ke dalamnyasys.path
. Misal dengansys.path = [..., '.../foo', '.../bar.zip']
, telur masuk.../foo
, tapi paket masukbar.zip
juga bisa diimpor. Anda tidak dapat menggunakanpkg_resources
untuk mengekstrak data dari paket dalambar.zip
. Saya belum memeriksa apakah setuptools mendaftarkan loader yang diperlukan untukimportlib.resources
bekerja dengan telur.Package has no location
muncul?templates
pada contoh), maka Anda dapat mengaturpackage
argumen ke__package__
, misalnyapkg_resources.read_text(__package__, 'temp_file')
Awal pengemasan:
Sebelum Anda bahkan khawatir tentang membaca file sumber daya, langkah pertama adalah memastikan bahwa file data telah dikemas ke dalam distribusi Anda - mudah untuk membacanya langsung dari struktur pohon sumber, tetapi bagian yang penting adalah membuat pastikan file sumber daya ini dapat diakses dari kode dalam paket yang diinstal .
Susun proyek Anda seperti ini, letakkan file data ke dalam subdirektori di dalam paket:
Anda harus lulus
include_package_data=True
dalamsetup()
panggilan. File manifes hanya diperlukan jika Anda ingin menggunakan setuptools / distutils dan membangun distribusi sumber. Untuk memastikantemplates/temp_file
paket tersebut dikemas untuk contoh struktur proyek ini, tambahkan baris seperti ini ke dalam file manifes:Catatan penting historis: Menggunakan file manifes tidak diperlukan untuk backend build modern seperti flit, poetry, yang akan menyertakan file data paket secara default. Jadi, jika Anda menggunakan
pyproject.toml
dan tidak memilikisetup.py
file maka Anda dapat mengabaikan semua hal tentangMANIFEST.in
.Sekarang, dengan mengemasnya, ke bagian bacaan ...
Rekomendasi:
Gunakan
pkgutil
API perpustakaan standar . Ini akan terlihat seperti ini di kode perpustakaan:Ini berfungsi dalam ritsleting. Ia bekerja pada Python 2 dan Python 3. Ia tidak membutuhkan ketergantungan pihak ketiga. Saya tidak benar-benar mengetahui kerugian apa pun (jika ya, silakan komentari jawabannya).
Cara buruk untuk menghindari:
Cara buruk # 1: menggunakan jalur relatif dari file sumber
Saat ini adalah jawaban yang diterima. Paling banter, tampilannya seperti ini:
Apa yang salah dengan itu? Asumsi bahwa Anda memiliki file dan subdirektori tidak benar. Pendekatan ini tidak berfungsi jika menjalankan kode yang dikemas dalam zip atau roda, dan mungkin sepenuhnya di luar kendali pengguna apakah paket Anda diekstrak ke sistem file atau tidak.
Cara buruk # 2: menggunakan pkg_resources API
Ini dijelaskan dalam jawaban pilihan teratas. Ini terlihat seperti ini:
Apa yang salah dengan itu? Ini menambahkan dependensi runtime pada setuptools , yang seharusnya hanya dependensi waktu instal . Mengimpor dan menggunakan
pkg_resources
bisa menjadi sangat lambat, karena kode membangun satu set yang berfungsi dari semua paket yang diinstal, meskipun Anda hanya tertarik pada sumber paket Anda sendiri . Itu bukan masalah besar pada waktu penginstalan (karena penginstalan hanya sekali), tetapi jelek saat runtime.Cara buruk # 3: Menggunakan API importlib.resources
Saat ini, ini adalah rekomendasi dalam jawaban pilihan teratas. Ini adalah tambahan pustaka standar baru-baru ini ( baru di Python 3.7 ), tetapi ada backport yang tersedia juga. Ini terlihat seperti ini:
Apa yang salah dengan itu? Sayangnya, itu belum berhasil ... Ini masih merupakan API yang tidak lengkap, penggunaan
importlib.resources
akan mengharuskan Anda untuk menambahkan file kosongtemplates/__init__.py
agar file data akan berada di dalam sub-paket daripada di subdirektori. Ini juga akan mengekspospackage/templates
subdirektori sebagaipackage.templates
sub-paket yang dapat diimpor dengan sendirinya. Jika itu bukan masalah besar dan itu tidak mengganggu Anda, Anda dapat melanjutkan dan menambahkan__init__.py
file di sana dan menggunakan sistem impor untuk mengakses sumber daya. Namun, saat Anda melakukannya, Anda dapat membuatnya menjadimy_resources.py
file, dan cukup mendefinisikan beberapa byte atau variabel string dalam modul, lalu mengimpornya dalam kode Python. Ini adalah sistem impor yang melakukan pekerjaan berat di sini.Contoh proyek:
Saya telah membuat proyek contoh di github dan mengunggahnya di PyPI , yang menunjukkan keempat pendekatan yang dibahas di atas. Cobalah dengan:
Lihat https://github.com/wimglenn/resources-example untuk info lebih lanjut.
sumber
importlib.resources
meskipun semua kekurangan ini dengan API yang tidak lengkap yang sudah menunggu penghentian ? Lebih baru belum tentu lebih baik. Beri tahu saya keuntungan apa yang sebenarnya ditawarkan dibandingkan stdlib pkgutil, yang tidak disebutkan dalam jawaban Anda?pkgutil.get_data()
mengkonfirmasi firasat saya - ini adalah API yang belum berkembang dan harus ditinggalkan. Yang mengatakan, saya setuju dengan Anda,importlib.resources
bukanlah alternatif yang jauh lebih baik, tetapi sampai PY3.10 menyelesaikan ini, saya mendukung pilihan ini, heving belajar bahwa ini bukan hanya "standar" lain yang direkomendasikan oleh dokumen.pkgutil
tidak disebutkan sama sekali pada jadwal penghentian PEP 594 - Melepaskan baterai mati dari pustaka standar , dan tidak mungkin dilepas tanpa alasan yang kuat. Itu sudah ada sejak Python 2.3 dan ditetapkan sebagai bagian dari protokol loader di PEP 302 . Menggunakan "API yang tidak didefinisikan" bukanlah jawaban yang sangat meyakinkan, yang bisa menggambarkan sebagian besar pustaka standar Python!pkgutil
hampir semua hal. "Naluri" dan seruan Anda kepada otoritas tidak ada artinya bagi saya, jika ada masalah denganget_data
loader, tunjukkan bukti dan contoh praktis.Jika Anda memiliki struktur ini
Anda membutuhkan kode ini:
Bagian aneh "selalu gunakan garis miring" berasal dari
setuptools
APIJika Anda bertanya-tanya di mana dokumentasinya:
sumber
pkg_resources
memiliki overhead yangpkgutil
mengatasi. Juga, jika kode yang disediakan dijalankan sebagai titik masuk,__name__
akan mengevaluasi ke__main__
, bukan nama paket.Konten dalam "10.8. Membaca Datafiles Dalam Paket" dari Python Cookbook, Edisi Ketiga oleh David Beazley dan Brian K. Jones memberikan jawabannya.
Saya hanya akan membawanya ke sini:
Misalkan Anda memiliki paket dengan file yang diatur sebagai berikut:
Sekarang misalkan file spam.py ingin membaca konten file somedata.dat. Untuk melakukannya, gunakan kode berikut:
Data variabel yang dihasilkan akan menjadi string byte yang berisi konten mentah file.
Argumen pertama untuk get_data () adalah string yang berisi nama paket. Anda dapat memasukkannya secara langsung atau menggunakan variabel khusus, seperti
__package__
. Argumen kedua adalah nama relatif file di dalam paket. Jika perlu, Anda dapat menavigasi ke direktori yang berbeda menggunakan konvensi nama file Unix standar selama direktori terakhir masih berada di dalam paket.Dengan cara ini, paket dapat diinstal sebagai direktori, .zip atau .egg.
sumber
Jawaban yang diterima harus digunakan
importlib.resources
.pkgutil.get_data
juga membutuhkan argumenpackage
sebagai paket non-namespace ( lihat pkgutil docs ). Karenanya, direktori yang berisi sumber daya harus memiliki__init__.py
file, sehingga memiliki batasan yang sama persis sepertiimportlib.resources
. Jika masalah overheadpkg_resources
tidak menjadi perhatian, ini juga merupakan alternatif yang dapat diterima.sumber
Setiap modul python dalam paket Anda memiliki
__file__
atributAnda dapat menggunakannya sebagai:
Untuk sumber daya telur, lihat: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources
sumber
dengan asumsi Anda menggunakan file telur; tidak diekstrak:
Saya "memecahkan" ini dalam proyek baru-baru ini, dengan menggunakan skrip pasca-instalasi, yang mengekstrak template saya dari telur (file zip) ke direktori yang sesuai di sistem file. Itu adalah solusi tercepat dan paling andal yang saya temukan, karena bekerja dengan
__path__[0]
terkadang bisa salah (saya tidak ingat namanya, tetapi saya menemukan setidaknya satu perpustakaan, yang menambahkan sesuatu di depan daftar itu!).File telur juga biasanya diekstrak dengan cepat ke lokasi sementara yang disebut "cache telur". Anda dapat mengubah lokasi itu menggunakan variabel lingkungan, baik sebelum memulai skrip Anda atau bahkan nanti, mis.
Namun ada pkg_resources yang mungkin melakukan pekerjaan dengan benar.
sumber