Paket alt dizinindeki verilere erişin


130

Veri dosyalarını bir ./data/alt dizinde açması gereken modüller içeren bir python paketi yazıyorum . Şu anda, sınıflarıma ve işlevlerime kodlanmış dosyaların yolları var. Kullanıcının sisteminde nereye kurulursa kurulsun alt dizine erişebilen daha sağlam bir kod yazmak istiyorum.

Çeşitli yöntemler denedim ama şimdiye kadar hiç şansım olmadı. Görünüşe göre "geçerli dizin" komutlarının çoğu, modülün dizinini değil, sistemin python yorumlayıcısının dizinini döndürür.

Bu önemsiz, yaygın bir sorun gibi görünüyor. Yine de anlayamıyorum. Sorunun bir kısmı, veri dosyalarımın dosya olmaması .py, bu nedenle içe aktarma işlevlerini ve benzerlerini kullanamıyorum.

Baska öneri?

Şu anda paket dizinim şöyle görünüyor:

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

Erişmeye çalışıyorum data.txtdan module*.py!


Yanıtlar:


24

__file__Paketin yolunu şu şekilde almak için kullanabilirsiniz :

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
Dosyalar bir dağıtımdaysa (IE. Egg) bu işe yaramayacaktır. Veri dosyasına ulaşmak için pkg_resources kullanın.
Chris

2
Gerçekten de bu bozuldu.
Federico

1
Ayrıca, __file__değer zip dosyasının yolu olacağından py2exe ile çalışmaz.
Pod

1
Bu aslında benim için çalıştı. Herhangi bir sorun yaşamadım. Python 3.6 kullanıyorum
Jorge

1
Bu dağıtım durumunda (yumurta vb.) Çalışmayacaktır.
Adarsh ​​Trivedi

166

Bunu yapmanın standart yolu, setuptools paketleri ve pkg_resources kullanmaktır.

Paketinizi aşağıdaki hiyerarşiye göre düzenleyebilir ve paket kurulum dosyasını, bu bağlantıya göre veri kaynaklarınızı gösterecek şekilde yapılandırabilirsiniz:

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

Daha sonra, bu bağlantıya göre pkg_resources kullanarak bu dosyaları yeniden bulabilir ve kullanabilirsiniz:

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
Olmaz pkg_resources bir çalışma zamanı bağımlılık oluşturmak setuptools ? Örneğin, bir Debian paketini yeniden dağıtıyorum, öyleyse neden python-setuptoolssadece bunun için güveneyim? Şimdiye kadar __file__benim için iyi çalışıyor.
mlt

4
Neden bu daha iyi: ResourceManager sınıfı, bu kaynaklar ister dosyalar ve dizinler olarak var olsun, ister bir tür arşivde sıkıştırılmış olsun, paket kaynaklarına tek tip erişim sağlar
vrdhn

4
Harika bir öneri, teşekkürler. Kullanarak açık standart bir dosya uyguladımfrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
eageranalyst

5
Bu, kurulu olmadığında paketi kullanmak için nasıl çalışacak? Sadece yerel olarak test etmek demek istiyorum
Claudiu

11
Python 3.7'de, bu amaç için importlib.resourcesyerini alır pkg_resources(performans sorunları nedeniyle).
benjimin

13

Bugün çalışan bir çözüm sağlamak. Tüm bu tekerlekleri yeniden icat etmemek için kesinlikle bu API'yi kullanın.

Gerçek bir dosya sistemi dosya adı gereklidir. Sıkıştırılmış yumurtalar bir önbellek dizinine çıkarılacaktır:

from pkg_resources import resource_filename, Requirement

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

Belirtilen kaynak için okunabilir bir dosya benzeri nesne döndürür; gerçek bir dosya, bir StringIO veya benzer bir nesne olabilir. Akış, kaynakta ne olursa olsun baytların olduğu gibi okunacağı anlamında "ikili moddadır".

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 kullanarak Paket Keşfi ve Kaynak Erişimi


10

Orada gelmez ayrıntıları kodu olduğunu bir cevap yapımında noktası çoğu kez değil değil olarak çalışmak, ama bu bir istisna olduğuna inanıyorum. Yerine importlib.resourcesgeçmesi gereken Python 3.7 eklendi pkg_resources. Adlarında eğik çizgi olmayan paketler içindeki dosyalara erişmek için çalışır , yani

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

yani data2.txtpaketin içine fooörneğin

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

ama bir istisna dışında başarısız olur

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

Bu yerleştirme hariç tamir edilemez __init__.pyolarak databir paket olarak kullanarak ve daha sonra:

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

Bu davranışın nedeni "tasarım gereğidir" ; ama tasarım değişebilir ...


"Tasarım gereği" için bir youtube videosundan daha iyi bir bağlantınız var mı - tercihen metin içeren bir bağlantı?
gerrit

@gerrit, ikincisi metin içeriyor. "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

Tüm modülünüz için bir isme ihtiyacınız var, size verilen dizin ağacı bu detayı listelemiyor, benim için bu işe yaradı:

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

Setuptools, dosyaları paketlenmiş veri dosyalarıyla bir ad eşleşmesine dayalı olarak çözümlemiyor gibi görünmüyor, bu nedenle, data/ne olursa olsun hemen hemen öneki eklemeniz gerekiyor . os.path.join('data', 'data.txt)Alternatif dizin ayırıcılara ihtiyacınız varsa kullanabilirsiniz , Genel olarak sabit kodlu unix stili dizin ayırıcılarla uyum sorunu bulmuyorum.


docs.python.org/3.6/distutils/… > Kurulum betiğinde sağlanan tüm yol adlarının (dosyalar veya dizinler) Unix kuralı kullanılarak, yani eğik çizgiyle ayrılmış şekilde yazılması gerektiğini unutmayın. Distutils, gerçekte yol adını kullanmadan önce bu platformdan bağımsız temsili mevcut platformunuzda uygun olana dönüştürmekle ilgilenecektir. Bu, kurulum betiğinizi işletim sistemleri arasında taşınabilir hale getirir; bu, elbette Distutils'in ana hedeflerinden biridir. Bu anlayışla, bu belgedeki tüm yol adları eğik çizgiyle ayrılmıştır.
changyuheng

6

Sanırım bir cevap buldum.

Aşağıdakileri içeren diğer modüllerime aktardığım bir data_path.py modülü oluşturuyorum:

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

Ve sonra tüm dosyalarımı açıyorum

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

2
Bu, kaynak bir arşiv dağıtımındayken (sıkıştırılmış yumurta gibi) çalışmayacaktır. Böyle bir şey tercih edin:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis

@ankostis setuptools, bir __file__yerde kullandığınızı algılarsa arşivi çıkaracak kadar akıllıdır . Benim durumumda, akışları değil, gerçekten yolları isteyen bir kitaplık kullanıyorum. Tabii ki dosyaları geçici olarak diske yazabilirim ama tembel olduğum için sadece setuptools'un özelliğini kullanıyorum.
letmaik
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.