จะอ่านไฟล์ (แบบคงที่) จากภายในแพ็คเกจ Python ได้อย่างไร


108

คุณช่วยบอกฉันได้ไหมว่าฉันจะอ่านไฟล์ที่อยู่ในแพ็คเกจ Python ของฉันได้อย่างไร

สถานการณ์ของฉัน

แพ็กเกจที่ฉันโหลดมีเทมเพลตจำนวนหนึ่ง (ไฟล์ข้อความที่ใช้เป็นสตริง) ที่ฉันต้องการโหลดจากภายในโปรแกรม แต่ฉันจะระบุเส้นทางไปยังไฟล์ดังกล่าวได้อย่างไร?

ลองนึกภาพว่าฉันต้องการอ่านไฟล์จาก:

package\templates\temp_file

การจัดการเส้นทางบางประเภท? การติดตามเส้นทางฐานแพ็กเกจ?



คำตอบ:


-13

[เพิ่ม 2016-06-15: ดูเหมือนว่าจะใช้ไม่ได้ในทุกสถานการณ์ โปรดดูคำตอบอื่น ๆ ]


import os, mypackage
template = os.path.join(mypackage.__path__[0], 'templates', 'temp_file')

177

TLDR; ใช้importlib.resourcesโมดูลของไลบรารีมาตรฐานตามที่อธิบายไว้ในวิธีที่ 2 ด้านล่าง

แบบดั้งเดิม pkg_resourcesจากsetuptoolsไม่แนะนำอีกต่อไปเพราะวิธีการใหม่:

  • มันเป็นอย่างมีนัยสำคัญมากขึ้น performant ;
  • ปลอดภัยกว่าเนื่องจากการใช้แพ็กเกจ (แทนที่จะใช้ path-stings) ทำให้เกิดข้อผิดพลาดเวลาคอมไพล์
  • มันง่ายกว่าเพราะคุณไม่ต้อง "เข้าร่วม" เส้นทาง;
  • เร็วกว่าเมื่อพัฒนาเนื่องจากคุณไม่ต้องการการพึ่งพาเพิ่มเติม ( setuptools) แต่ต้องพึ่งพาไลบรารีมาตรฐานของ Python เพียงอย่างเดียว

ฉันเก็บรายการแบบดั้งเดิมไว้ก่อนเพื่ออธิบายความแตกต่างของวิธีการใหม่เมื่อย้ายรหัสที่มีอยู่ ( อธิบายการพอร์ตด้วย)



สมมติว่าเทมเพลตของคุณอยู่ในโฟลเดอร์ที่ซ้อนอยู่ภายในแพ็คเกจของโมดูลของคุณ:

  <your-package>
    +--<module-asking-the-file>
    +--templates/
          +--temp_file                         <-- We want this file.

หมายเหตุ 1:แน่นอนว่าเราไม่ควรใช้__file__แอตทริบิวต์ (เช่นรหัสจะแตกเมื่อเสิร์ฟจาก zip)

หมายเหตุ 2:หากคุณกำลังสร้างแพ็คเกจนี้อย่าลืมปฏิเสธไฟล์ข้อมูลของคุณเป็นpackage_dataหรือdata_filesในไฟล์setup.py.

1) ใช้pkg_resourcesจากsetuptools(ช้า)

คุณอาจใช้pkg_resourcesแพ็คเกจจากการแจกจ่ายsetuptoolsแต่มาพร้อมกับต้นทุนและประสิทธิภาพที่ชาญฉลาด :

import pkg_resources

# Could be any dot-separated package/module name or a "Requirement"
resource_package = __name__
resource_path = '/'.join(('templates', 'temp_file'))  # Do not use os.path.join()
template = pkg_resources.resource_string(resource_package, resource_path)
# or for a file-like stream:
template = pkg_resources.resource_stream(resource_package, resource_path)

เคล็ดลับ:

  • สิ่งนี้จะอ่านข้อมูลแม้ว่าการกระจายของคุณจะถูกบีบอัดดังนั้นคุณสามารถตั้งค่า zip_safe=Trueในของคุณsetup.pyและ / หรือใช้zipappแพ็คเกอร์ที่รอคอยมานานจากpython-3.5เพื่อสร้างการแจกแจงที่มีอยู่ในตัว

  • อย่าลืมเพิ่มsetuptoolsข้อกำหนดในการรันไทม์ของคุณ (เช่นใน install_requires`)

... และสังเกตว่าตาม Setuptools / pkg_resourcesdocs คุณไม่ควรใช้os.path.join:

การเข้าถึงทรัพยากรขั้นพื้นฐาน

โปรดทราบว่าชื่อรีซอร์สต้อง/แยกพา ธ และต้องไม่เป็นค่าสัมบูรณ์ (เช่นไม่มีนำหน้า/) หรือมีชื่อสัมพัทธ์เช่น " .." ไม่ได้ใช้os.pathการปฏิบัติในการจัดการเส้นทางทรัพยากรที่พวกเขาจะไม่ได้เป็นเส้นทางระบบแฟ้ม

2) Python> = 3.7 หรือใช้importlib_resourcesไลบรารีbackported

ใช้importlib.resourcesโมดูลของไลบรารีมาตรฐานซึ่งมีประสิทธิภาพมากกว่าsetuptoolsข้างต้น:

try:
    import importlib.resources as pkg_resources
except ImportError:
    # Try backported to PY<37 `importlib_resources`.
    import importlib_resources as pkg_resources

from . import templates  # relative-import the *package* containing the templates

template = pkg_resources.read_text(templates, 'temp_file')
# or for a file-like stream:
template = pkg_resources.open_text(templates, 'temp_file')

ความสนใจ:

เกี่ยวกับฟังก์ชั่นread_text(package, resource):

  • packageสามารถเป็นได้ทั้งสตริงหรือโมดูล
  • resourceไม่ได้เป็นเส้นทางอีกต่อไป แต่เพียงชื่อไฟล์ของทรัพยากรที่จะเปิดภายในแพคเกจที่มีอยู่ อาจไม่มีตัวคั่นพา ธ และอาจไม่มีทรัพยากรย่อย (กล่าวคือไม่สามารถเป็นไดเร็กทอรี)

สำหรับตัวอย่างที่ถามในคำถามตอนนี้เราต้อง:

  • สร้าง<your_package>/templates/ เป็นแพ็กเกจที่เหมาะสมโดยสร้าง__init__.pyไฟล์ว่างในนั้น
  • ดังนั้นตอนนี้เราสามารถใช้คำสั่งง่ายๆ (อาจเป็นญาติกัน) import(ไม่มีการแยกวิเคราะห์ชื่อแพ็คเกจ / โมดูลอีกต่อไป)
  • และเพียงแค่ขอresource_name = "temp_file"(ไม่มีเส้นทาง)

เคล็ดลับ:

  • ในการเข้าถึงไฟล์ภายในโมดูลปัจจุบันให้ตั้งค่าอาร์กิวเมนต์แพ็กเกจเป็น__package__เช่นpkg_resources.read_text(__package__, 'temp_file')(ขอบคุณ @ ben-mares)
  • สิ่งต่างๆน่าสนใจเมื่อมีการถามชื่อไฟล์จริงpath()เนื่องจากตอนนี้ตัวจัดการบริบทถูกใช้สำหรับไฟล์ที่สร้างขึ้นชั่วคราว (อ่านสิ่งนี้ )
  • เพิ่มไลบรารี backported ตามเงื่อนไขสำหรับ Pythons รุ่นเก่าด้วยinstall_requires=[" importlib_resources ; python_version<'3.7'"](ตรวจสอบสิ่งนี้หากคุณรวมโครงการของคุณด้วยsetuptools<36.2.1)
  • อย่าลืมลบsetuptoolsไลบรารีออกจากข้อกำหนดรันไทม์ของคุณหากคุณย้ายจากวิธีการดั้งเดิม
  • โปรดจำไว้ว่าการปรับแต่งsetup.pyหรือMANIFESTจะรวมไฟล์แบบคงที่ใด
  • คุณสามารถตั้งค่าzip_safe=Trueในsetup.pyไฟล์.

1
str.join ใช้ลำดับ resource_path = '/'.join(('templates', 'temp_file'))
Alex Punnen

2
ฉันได้รับNotImplementedError: Can't perform this operation for loaders without 'get_data()'ความคิดใด ๆ ?
leoschet

โปรดทราบว่าimportlib.resourcesและpkg_resourcesมีไม่จำเป็นต้องเข้ากันได้ importlib.resourcesทำงานร่วมกับ zipfiles เพิ่มไปยังsys.path, setuptools และpkg_resourcesการทำงานกับไฟล์ไข่ซึ่งเป็น zipfiles sys.pathเก็บไว้ในไดเรกทอรีที่ตัวเองจะถูกเพิ่ม เช่นsys.path = [..., '.../foo', '.../bar.zip']ไข่เข้าไป.../fooแต่bar.zipสามารถนำเข้าบรรจุภัณฑ์ได้ คุณไม่สามารถใช้pkg_resourcesเพื่อดึงข้อมูลจากแพ็กเกจในbar.zip. ฉันไม่ได้ตรวจสอบว่า setuptools ลงทะเบียนตัวโหลดที่จำเป็นสำหรับimportlib.resourcesการทำงานกับไข่หรือไม่
Martijn Pieters

จำเป็นต้องมีการกำหนดค่า setup.py เพิ่มเติมPackage has no locationหรือไม่หากข้อผิดพลาดปรากฏขึ้น
zygimantus

1
ในกรณีที่คุณต้องการเข้าถึงไฟล์ภายในโมดูลปัจจุบัน (และไม่ใช่โมดูลย่อยเหมือนtemplatesตามตัวอย่าง) คุณสามารถตั้งค่าpackageอาร์กิวเมนต์เป็น__package__เช่นpkg_resources.read_text(__package__, 'temp_file')
Ben Mares

46

พรีลูดบรรจุภัณฑ์:

ก่อนที่คุณจะกังวลเกี่ยวกับการอ่านไฟล์ทรัพยากรขั้นตอนแรกคือตรวจสอบให้แน่ใจว่าไฟล์ข้อมูลได้รับการรวมเข้ากับการแจกจ่ายของคุณตั้งแต่แรกคุณสามารถอ่านได้โดยตรงจากแผนผังต้นทาง แต่ส่วนที่สำคัญคือการทำ แน่ใจว่าไฟล์ทรัพยากรเหล่านี้สามารถเข้าถึงได้จากโค้ดภายในแพ็คเกจที่ติดตั้ง

จัดโครงสร้างโครงการของคุณเช่นนี้โดยวางไฟล์ข้อมูลลงในไดเร็กทอรีย่อยภายในแพ็คเกจ:

.
├── package
   ├── __init__.py
   ├── templates
      └── temp_file
   ├── mymodule1.py
   └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py

คุณควรผ่านinclude_package_data=Trueในการsetup()โทร ไฟล์รายการจำเป็นเฉพาะเมื่อคุณต้องการใช้ setuptools / distutils และสร้างการแจกแจงซอร์ส เพื่อให้แน่ใจว่าtemplates/temp_fileได้รับแพ็กเกจสำหรับโครงสร้างโปรเจ็กต์ตัวอย่างนี้ให้เพิ่มบรรทัดเช่นนี้ลงในไฟล์รายการ:

recursive-include package *

บันทึกย่อทางประวัติศาสตร์: การใช้ไฟล์ manifest ไม่จำเป็นสำหรับแบ็กเอนด์บิลด์สมัยใหม่เช่น flit, poetry ซึ่งจะรวมไฟล์ข้อมูลแพ็กเกจไว้ตามค่าเริ่มต้น ดังนั้นถ้าคุณกำลังใช้pyproject.tomlและคุณไม่ได้มีไฟล์แล้วคุณสามารถละเว้นทุกสิ่งที่เกี่ยวกับsetup.pyMANIFEST.in

ตอนนี้มีบรรจุภัณฑ์ไม่อยู่ในส่วนการอ่าน ...

คำแนะนำ:

ใช้pkgutilAPI ของไลบรารีมาตรฐาน จะมีลักษณะเช่นนี้ในรหัสห้องสมุด:

# within package/mymodule1.py, for example
import pkgutil

data = pkgutil.get_data(__name__, "templates/temp_file")

ทำงานเป็นซิป ทำงานบน Python 2 และ Python 3 ไม่ต้องการการอ้างอิงของบุคคลที่สาม ฉันไม่ทราบถึงข้อเสียใด ๆ (ถ้าคุณเป็นเช่นนั้นโปรดแสดงความคิดเห็นในคำตอบ)

วิธีหลีกเลี่ยงที่ไม่ดี:

วิธีที่ไม่ดี # 1: ใช้เส้นทางสัมพัทธ์จากไฟล์ต้นฉบับ

ขณะนี้เป็นคำตอบที่ยอมรับ อย่างดีที่สุดก็มีลักษณะดังนี้:

from pathlib import Path

resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()

มันผิดอะไร? สมมติฐานว่าคุณมีไฟล์และไดเร็กทอรีย่อยที่พร้อมใช้งานไม่ถูกต้อง วิธีนี้ใช้ไม่ได้หากเรียกใช้โค้ดที่บรรจุใน zip หรือ wheel และอาจอยู่นอกเหนือการควบคุมของผู้ใช้ไม่ว่าแพ็กเกจของคุณจะถูกแตกออกไปยังระบบไฟล์หรือไม่

วิธีที่ไม่ดี # 2: ใช้ pkg_resources API

สิ่งนี้อธิบายไว้ในคำตอบที่ได้รับการโหวตสูงสุด มีลักษณะดังนี้:

from pkg_resources import resource_string

data = resource_string(__name__, "templates/temp_file")

มันผิดอะไร? เพิ่มการพึ่งพารันไทม์บนsetuptoolsซึ่งควรเป็นการพึ่งพาเวลาติดตั้งเท่านั้น การนำเข้าและใช้งานpkg_resourcesอาจช้ามากเนื่องจากโค้ดสร้างชุดที่ใช้งานได้ของแพ็คเกจที่ติดตั้งทั้งหมดแม้ว่าคุณจะสนใจเฉพาะทรัพยากรแพ็คเกจของคุณเองก็ตาม นั่นไม่ใช่เรื่องใหญ่ในเวลาติดตั้ง (เนื่องจากการติดตั้งครั้งเดียวปิด) แต่มันน่าเกลียดที่รันไทม์

วิธีที่ไม่ดี # 3: การใช้ importlib.resources APIs

นี่คือคำแนะนำในคำตอบที่ได้รับการโหวตสูงสุด เป็นการเพิ่มไลบรารีมาตรฐานล่าสุด( ใหม่ใน Python 3.7 ) ดูเหมือนว่า:

from importlib.resources import read_binary

data = read_binary("package.templates", "temp_file")

มันผิดอะไร? น่าเสียดายที่มันใช้ไม่ได้ ... นี่ยังคงเป็น API ที่ไม่สมบูรณ์การใช้importlib.resourcesจะทำให้คุณต้องเพิ่มไฟล์เปล่าtemplates/__init__.pyเพื่อให้ไฟล์ข้อมูลนั้นอยู่ภายในแพ็กเกจย่อยแทนที่จะอยู่ในไดเร็กทอรีย่อย นอกจากนี้ยังแสดงpackage/templatesไดเร็กทอรีย่อยเป็นpackage.templatesแพ็กเกจย่อยที่นำเข้าได้ในสิทธิ์ของตนเอง หากนั่นไม่ใช่เรื่องใหญ่และไม่รบกวนคุณคุณสามารถดำเนินการต่อและเพิ่ม__init__.pyไฟล์ที่นั่นและใช้ระบบนำเข้าเพื่อเข้าถึงทรัพยากร อย่างไรก็ตามในขณะที่คุณกำลังทำอยู่คุณสามารถทำให้เป็นmy_resources.pyไฟล์แทนได้และกำหนดไบต์หรือตัวแปรสตริงในโมดูลจากนั้นนำเข้าในโค้ด Python เป็นระบบนำเข้าที่ทำการยกของหนักที่นี่ไม่ว่าจะด้วยวิธีใดก็ตาม

รางวัลชมเชย: การใช้ importlib_resources API ที่ใหม่กว่า

สิ่งนี้ยังไม่ได้กล่าวถึงในคำตอบอื่น ๆ แต่importlib_resourcesเป็นมากกว่า backport ธรรมดาของimportlib.resourcesโค้ดPython 3.7+ มี API แบบข้ามได้ซึ่งคุณสามารถใช้ได้ดังนี้:

import importlib_resources

my_resources = importlib_resources.files("package")
data = (my_resources / "templates" / "temp_file").read_bytes()

สิ่งนี้ใช้ได้กับ Python 2 และ 3 ทำงานในซิปและไม่จำเป็นต้อง__init__.pyเพิ่มไฟล์ปลอมในไดเร็กทอรีย่อยของทรัพยากร ข้อเสียเพียงอย่างเดียวกับpkgutilที่ฉันเห็นคือ API ใหม่เหล่านี้ยังไม่มาถึง stdlib ดังนั้นจึงยังมีการพึ่งพาของบุคคลที่สาม API ใหม่กว่าimportlib_resourcesควรมาถึง stdlib importlib.resourcesใน Python 3.9

ตัวอย่างโครงการ:

ฉันได้สร้างโครงการตัวอย่างบนgithubและอัปโหลดบนPyPIซึ่งแสดงให้เห็นถึงแนวทางทั้งห้าที่กล่าวถึงข้างต้น ลองใช้กับ:

$ pip install resources-example
$ resources-example

ดูhttps://github.com/wimglenn/resources-exampleสำหรับข้อมูลเพิ่มเติม


1
มีการแก้ไขเมื่อเดือนพฤษภาคมที่ผ่านมา แต่ฉันเดาว่ามันง่ายที่จะพลาดคำอธิบายในบทนำ ถึงกระนั้นคุณก็แนะนำคนที่ต่อต้านมาตรฐานนั่นเป็นกระสุนที่ยากที่จะกัด :-)
ankostis

1
@ankostis ผมขอเปิดคำถามกับคุณแทนทำไมคุณอยากจะแนะนำimportlib.resourcesแม้จะมีข้อบกพร่องเหล่านี้ทั้งหมดที่มี API ที่ไม่สมบูรณ์ที่มีอยู่แล้วที่รอดำเนินการเลิก ? ใหม่กว่าไม่จำเป็นต้องดีกว่า บอกฉันว่ามันมีข้อดีอะไรมากกว่า stdlib pkgutil ซึ่งคำตอบของคุณไม่ได้กล่าวถึง?
Wim

1
เรียน @wim คำตอบสุดท้ายของ Brett Canonเกี่ยวกับการใช้การpkgutil.get_data()ยืนยันความรู้สึกของฉัน - มันเป็น API ที่ยังด้อยพัฒนาและจะเลิกใช้งาน ที่กล่าวว่าฉันเห็นด้วยกับคุณimportlib.resourcesไม่ใช่ทางเลือกที่ดีกว่ามากนัก แต่จนกว่า PY3.10 จะแก้ไขปัญหานี้ได้ฉันก็ยืนตามทางเลือกนี้เนื่องจากได้เรียนรู้ว่าเอกสารนี้ไม่ใช่ "มาตรฐาน" อื่นที่แนะนำ
ankostis

1
@ankostis ฉันจะใช้ความคิดเห็นของ Brett กับเม็ดเกลือ pkgutilไม่ได้กล่าวถึงเลยในกำหนดการเลิกใช้งานของPEP 594 - การนำแบตเตอรี่ที่ตายแล้วออกจากไลบรารีมาตรฐานและไม่น่าจะถูกถอดออกโดยไม่มีเหตุผลที่ดี มันได้รับรอบตั้งแต่ Python 2.3 และระบุว่าเป็นส่วนหนึ่งของโปรโตคอลสำหรับรถตักดินในPEP 302 การใช้ "API ที่ไม่ได้กำหนด" ไม่ใช่คำตอบที่น่าเชื่อถือซึ่งสามารถอธิบายไลบรารีมาตรฐาน Python ส่วนใหญ่ได้!
Wim

2
ให้ฉันเพิ่ม: ฉันต้องการเห็นทรัพยากร importlib ประสบความสำเร็จด้วย! ฉันทุกคนต้องการ API ที่กำหนดไว้อย่างเข้มงวด แค่ในสถานะปัจจุบันไม่สามารถแนะนำได้จริงๆ API ยังอยู่ระหว่างการเปลี่ยนแปลงไม่สามารถใช้งานได้กับแพ็กเกจที่มีอยู่จำนวนมากและใช้ได้เฉพาะใน Python รุ่นล่าสุดเท่านั้น ในทางปฏิบัติมันแย่กว่าpkgutilในทุกๆด้าน "ความรู้สึก" และการอุทธรณ์ต่ออำนาจของคุณไม่มีความหมายสำหรับฉันหากมีปัญหากับget_dataรถตักให้แสดงหลักฐานและตัวอย่างที่ใช้ได้จริง
Wim

14

ในกรณีที่คุณมีโครงสร้างนี้

lidtk
├── bin
   └── lidtk
├── lidtk
   ├── analysis
      ├── char_distribution.py
      └── create_cm.py
   ├── classifiers
      ├── char_dist_metric_train_test.py
      ├── char_features.py
      ├── cld2
         ├── cld2_preds.txt
         └── cld2wili.py
      ├── get_cld2.py
      ├── text_cat
         ├── __init__.py
         ├── README.md   <---------- say you want to get this
         └── textcat_ngram.py
      └── tfidf_features.py
   ├── data
      ├── __init__.py
      ├── create_ml_dataset.py
      ├── download_documents.py
      ├── language_utils.py
      ├── pickle_to_txt.py
      └── wili.py
   ├── __init__.py
   ├── get_predictions.py
   ├── languages.csv
   └── utils.py
├── README.md
├── setup.cfg
└── setup.py

คุณต้องการรหัสนี้:

import pkg_resources

# __name__ in case you're within the package
# - otherwise it would be 'lidtk' in this example as it is the package name
path = 'classifiers/text_cat/README.md'  # always use slash
filepath = pkg_resources.resource_filename(__name__, path)

ส่วน "always use slash" แปลก ๆ มาจากsetuptoolsAPI

โปรดสังเกตด้วยว่าหากคุณใช้เส้นทางคุณต้องใช้เครื่องหมายทับ (/) เป็นตัวคั่นเส้นทางแม้ว่าคุณจะใช้ Windows ก็ตาม Setuptools จะแปลงเครื่องหมายทับเป็นตัวคั่นเฉพาะแพลตฟอร์มที่เหมาะสมโดยอัตโนมัติในเวลาสร้าง

ในกรณีที่คุณสงสัยว่าเอกสารอยู่ที่ไหน:


ขอบคุณสำหรับคำตอบที่กระชับ
เปาโล

pkg_resourcesมีค่าใช้จ่ายที่pkgutilเอาชนะ นอกจากนี้หากรหัสที่ระบุถูกเรียกใช้เป็นจุดเริ่มต้น__name__จะประเมินเป็น__main__ไม่ใช่ชื่อแพ็กเกจ
A.Hendry

8

เนื้อหาใน "10.8. Reading Datafiles Within a Package" ของ Python Cookbook, Third Edition โดย David Beazley และ Brian K. Jones ให้คำตอบ

ฉันจะไปที่นี่:

สมมติว่าคุณมีแพ็กเกจที่มีการจัดระเบียบไฟล์ดังนี้:

mypackage/
    __init__.py
    somedata.dat
    spam.py

ตอนนี้สมมติว่าไฟล์ spam.py ต้องการอ่านเนื้อหาของไฟล์ somedata.dat โดยใช้รหัสต่อไปนี้:

import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')

ข้อมูลตัวแปรที่ได้จะเป็นสตริงไบต์ที่มีเนื้อหาดิบของไฟล์

อาร์กิวเมนต์แรกของ get_data () คือสตริงที่มีชื่อแพ็กเกจ คุณสามารถจัดหาโดยตรงหรือใช้ตัวแปรพิเศษเช่น__package__. อาร์กิวเมนต์ที่สองคือชื่อสัมพัทธ์ของไฟล์ภายในแพ็กเกจ หากจำเป็นคุณสามารถนำทางไปยังไดเร็กทอรีต่างๆโดยใช้รูปแบบชื่อไฟล์ Unix มาตรฐานได้ตราบเท่าที่ไดเร็กทอรีสุดท้ายยังคงอยู่ภายในแพ็กเกจ

ด้วยวิธีนี้แพคเกจสามารถติดตั้งเป็นไดเร็กทอรี, .zip หรือ. egg


ฉันชอบที่คุณอ้างอิงตำราอาหาร!
A.Hendry

-1

ทุกโมดูลหลามในแพ็คเกจของคุณมีไฟล์ __file__แอตทริบิวต์

คุณสามารถใช้เป็น:

import os 
from mypackage

templates_dir = os.path.join(os.path.dirname(mypackage.__file__), 'templates')
template_file = os.path.join(templates_dir, 'template.txt')

สำหรับทรัพยากรไข่โปรดดู: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources


1
สิ่งนี้จะใช้ไม่ได้กับซอร์สโค้ดที่อยู่ในไฟล์ zip
A.Hendry

-1

importlib.resourcesคำตอบที่ได้รับการยอมรับควรจะใช้งาน pkgutil.get_dataยังกำหนดให้อาร์กิวเมนต์packageเป็นแพ็กเกจที่ไม่ใช่เนมสเปซ ( ดูเอกสาร pkgutil ) ดังนั้นไดเร็กทอรีที่มีทรัพยากรจะต้องมี__init__.pyไฟล์ทำให้มีข้อ จำกัด เช่นเดียวกับimportlib.resources. หากปัญหาค่าใช้จ่ายpkg_resourcesไม่น่ากังวลนี่ก็เป็นทางเลือกที่ยอมรับได้เช่นกัน

Pre-Python-3.3แพ็คเกจทั้งหมดต้องมี__init__.pyไฟล์. Post-Python-3.3โฟลเดอร์ไม่จำเป็นต้อง__init__.pyเป็นแพ็คเกจ สิ่งนี้เรียกว่าไฟล์namespace package. น่าเสียดายที่pkgutilใช้งานไม่ได้namespace packages( ดูเอกสาร pkgutil )

ตัวอย่างเช่นด้วยโครงสร้างแพ็คเกจ:

+-- foo/
|   +-- __init__.py
|   +-- bar/
|   |   +-- hi.txt

ที่hi.txtมีเพียงHi!คุณจะได้รับต่อไปนี้

>>> import pkgutil
>>> rsrc = pkgutil.get_data("foo.bar", "hi.txt")
>>> print(rsrc)
None

อย่างไรก็ตามด้วยการ__init__.pyเข้าbarคุณจะได้รับ

>>> import pkgutil
>>> rsrc = pkgutil.get_data("foo.bar", "hi.txt")
>>> print(rsrc)
b'Hi!'

คำตอบนี้ไม่ถูกต้อง - ไดเร็กทอรีที่มีทรัพยากรไม่จำเป็นต้องเป็นแพ็กเกจ สามารถเป็นไดเร็กทอรีย่อยภายในแพ็กเกจ ข้อ จำกัด ของการimportlib.resourcesซึ่งpkgutilไม่ได้เป็นที่ไดเรกทอรีที่มีทรัพยากรที่ตัวเองต้องการที่จะมี__init__.pyมากเกินไปคือมันจะต้องมีแพ็กเกจย่อย ไม่เกี่ยวข้องกับปัญหาแพ็กเกจเนมสเปซซึ่งกังวลว่าจะมี__init__.pyไดเร็กทอรีระดับบนสุดแทนที่จะเป็นไดเร็กทอรีย่อยข้อมูลภายในแพ็กเกจ
Wim

@wim ฉันขอโทษ แต่ฉันเชื่อว่าคุณเข้าใจผิด pre-Python 3.3+แพคเกจทั้งหมดจะต้องมีการ__init__.pyโหลด โพสต์ 3.3 แพ็คเกจไม่จำเป็นต้องใช้ แพคเกจโดยไม่ต้องAre__init__.py namespace packagesตามpkgutilเอกสารถ้าคุณพยายามที่จะโหลดทรัพยากรจากแพคเกจ namespace Noneที่คุณจะได้รับ โปรดดูคำตอบที่แก้ไขแล้วของฉัน
A.Hendry

คุณใช้pkgutilไม่ถูกต้อง ลองด้วยpkgutil.get_data("foo", "bar/hi.txt")
wim

-3

สมมติว่าคุณใช้ไฟล์ไข่ ไม่ได้แยก:

ฉัน "แก้ไข" สิ่งนี้ในโปรเจ็กต์ล่าสุดโดยใช้สคริปต์ postinstall ซึ่งแยกเทมเพลตของฉันจาก egg (ไฟล์ zip) ไปยังไดเร็กทอรีที่เหมาะสมในระบบไฟล์ มันเป็นวิธีแก้ปัญหาที่รวดเร็วและน่าเชื่อถือที่สุดที่ฉันพบเนื่องจาก__path__[0]บางครั้งการทำงานกับอาจผิดพลาด (ฉันจำชื่อไม่ได้ แต่ฉันมองข้ามไลบรารีอย่างน้อยหนึ่งไลบรารีซึ่งเพิ่มบางอย่างไว้ด้านหน้ารายการนั้น!)

นอกจากนี้ไฟล์ไข่มักจะถูกแยกออกทันทีไปยังตำแหน่งชั่วคราวที่เรียกว่า "egg cache" คุณสามารถเปลี่ยนตำแหน่งนั้นโดยใช้ตัวแปรสภาพแวดล้อมทั้งก่อนเริ่มสคริปต์ของคุณหรือแม้กระทั่งในภายหลังเช่น

os.environ['PYTHON_EGG_CACHE'] = path

อย่างไรก็ตามมีpkg_resourcesที่อาจทำงานได้อย่างถูกต้อง

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