เข้าถึงข้อมูลในไดเรกทอรีย่อยของแพ็คเกจ


130

ฉันกำลังเขียนแพ็กเกจหลามพร้อมโมดูลที่จำเป็นต้องเปิดไฟล์ข้อมูลใน./data/ไดเรกทอรีย่อย ตอนนี้ฉันมีเส้นทางไปยังไฟล์ที่ฮาร์ดโค้ดในคลาสและฟังก์ชั่นของฉัน ฉันต้องการเขียนโค้ดที่มีประสิทธิภาพมากขึ้นซึ่งสามารถเข้าถึงไดเรกทอรีย่อยโดยไม่คำนึงถึงตำแหน่งที่ติดตั้งในระบบของผู้ใช้

ฉันลองใช้วิธีการต่าง ๆ แต่จนถึงตอนนี้ฉันไม่มีโชค ดูเหมือนว่าคำสั่ง "ไดเรกทอรีปัจจุบัน" ส่วนใหญ่จะส่งคืนไดเรกทอรีของตัวแปลหลามของระบบไม่ใช่ไดเรกทอรีของโมดูล

ดูเหมือนว่ามันควรจะเป็นปัญหาเล็กน้อยที่พบบ่อย ถึงกระนั้นฉันก็ไม่สามารถเข้าใจมันได้ ส่วนหนึ่งของปัญหาคือไฟล์ข้อมูลของฉันไม่ใช่.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
อันที่จริงมันก็ถูกทำลาย
Federico

1
และ__file__ไม่สามารถใช้งานกับ py2exe ได้เนื่องจากค่าจะเป็นพา ธ ไปยังไฟล์ zip
Pod

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

4
ทำไมจึงดีกว่า: คลาส ResourceManager ให้การเข้าถึงทรัพยากรแพ็คเกจอย่างสม่ำเสมอไม่ว่าจะเป็นทรัพยากรเหล่านั้นเป็นไฟล์และไดเรกทอรีหรือถูกบีบอัดในไฟล์เก็บถาวรบางประเภท
vrdhn

4
ข้อเสนอแนะที่ยอดเยี่ยมขอบคุณ ฉันใช้งานไฟล์มาตรฐานแบบเปิดโดยใช้from pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
eageranalyst

5
จะใช้งานแพคเกจนี้ได้อย่างไรเมื่อไม่ได้ติดตั้ง แค่ทดสอบเฉพาะที่ฉันหมายถึง
Claudiu

11
ใน python 3.7 importlib.resourcesแทนที่pkg_resourcesสำหรับวัตถุประสงค์นี้ (เนื่องจากปัญหาประสิทธิภาพ)
benjimin

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

มักจะไม่ชี้ในการตอบว่ารหัสรายละเอียดที่ไม่ทำงานตามที่เป็นอยู่ แต่ฉันเชื่อว่านี่เป็นข้อยกเว้น งูหลาม 3.7 เพิ่มที่ควรจะเปลี่ยนimportlib.resources pkg_resourcesมันจะทำงานสำหรับการเข้าถึงไฟล์ภายในแพ็คเกจที่ไม่มีเครื่องหมายทับในชื่อของพวกเขาเช่น

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__.pyในdataแล้วใช้มันเป็นแพคเกจ:

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 ไม่ปรากฏขึ้นเพื่อแก้ไขไฟล์ตามชื่อตรงกับไฟล์ข้อมูลที่บรรจุดังนั้นคุณ gunna ต้องรวมdata/คำนำหน้าสวยมากไม่ว่าอะไร คุณสามารถใช้os.path.join('data', 'data.txt)หากคุณต้องการตัวคั่นไดเรกทอรีอื่นโดยทั่วไปฉันพบว่าไม่มีปัญหาความเข้ากันได้กับตัวคั่นไดเรกทอรีลักษณะ unix


docs.python.org/3.6/distutils/ … > โปรดทราบว่าชื่อพา ธ (ไฟล์หรือไดเรกทอรี) ใด ๆ ที่ให้มาในสคริปต์การติดตั้งควรเขียนโดยใช้ระบบปฏิบัติการ Unix เช่นแยกด้วยสแลช Distutils จะดูแลการแปลงการนำเสนอแพลตฟอร์มที่เป็นกลางให้เป็นสิ่งที่เหมาะสมบนแพลตฟอร์มปัจจุบันของคุณก่อนที่จะใช้ชื่อพา ธ สิ่งนี้ทำให้สคริปต์การติดตั้งของคุณสามารถพกพาได้ในทุกระบบปฏิบัติการซึ่งแน่นอนว่าเป็นหนึ่งในเป้าหมายหลักของ Distutils ในจิตวิญญาณนี้ชื่อพา ธ ทั้งหมดในเอกสารนี้คั่นด้วยเครื่องหมายทับ
changyuheng

6

ฉันคิดว่าฉันตามล่าหาคำตอบ

ฉันสร้าง data_path.py โมดูลซึ่งฉันนำเข้าสู่โมดูลอื่นที่มี:

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

จากนั้นฉันก็เปิดไฟล์ทั้งหมดด้วย

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

2
สิ่งนี้จะล้มเหลวในการทำงานเมื่อทรัพยากรอยู่ในการกระจายการเก็บถาวร (เช่นไข่บีบอัด) ชอบสิ่งที่ต้องการ:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis

@ankostis setuptools ฉลาดพอที่จะแตกไฟล์เก็บถาวรหากตรวจพบว่าคุณใช้ที่__file__อื่น ในกรณีของฉันฉันใช้ห้องสมุดที่ต้องการเส้นทางจริงๆและไม่ใช่สตรีม แน่นอนฉันสามารถเขียนไฟล์ลงดิสก์ชั่วคราว แต่ก็ขี้เกียจฉันแค่ใช้คุณสมบัติของ setuptools
letmaik
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.