Truy cập dữ liệu trong thư mục con gói


130

Tôi đang viết một gói python với các mô-đun cần mở tệp dữ liệu trong ./data/thư mục con. Ngay bây giờ tôi có các đường dẫn đến các tệp được mã hóa vào các lớp và hàm của tôi. Tôi muốn viết mã mạnh mẽ hơn có thể truy cập thư mục con bất kể nó được cài đặt ở đâu trên hệ thống của người dùng.

Tôi đã thử nhiều phương pháp, nhưng cho đến nay tôi không gặp may. Dường như hầu hết các lệnh "thư mục hiện tại" trả về thư mục của trình thông dịch python của hệ thống chứ không phải thư mục của mô-đun.

Điều này có vẻ như nó phải là một vấn đề tầm thường, phổ biến. Nhưng tôi dường như không thể tìm ra nó. Một phần của vấn đề là các tệp dữ liệu của tôi không phải là .pytệp, vì vậy tôi không thể sử dụng các chức năng nhập và tương tự.

Bất kỳ đề xuất?

Ngay bây giờ thư mục gói của tôi trông như:

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

Tôi đang cố gắng truy cập data.txttừ module*.py!


Câu trả lời:


24

Bạn có thể sử dụng __file__để có được đường dẫn đến gói, như thế này:

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
Điều này sẽ không hoạt động nếu các tệp nằm trong một bản phân phối (IE. Egg). Sử dụng pkg_resource để lấy tệp dữ liệu.
Chris

2
Thật vậy, điều này đã bị phá vỡ.
Federico

1
Ngoài ra, __file__không hoạt động với py2exe, vì giá trị sẽ là đường dẫn đến tệp zip.
Pod

1
Điều này thực sự làm việc cho tôi. Không có bất kỳ vấn đề. Tôi đang sử dụng python 3.6
Jorge

1
Điều này sẽ không hoạt động trong trường hợp phân phối (trứng vv).
Adarsh ​​Trivingi

166

Cách tiêu chuẩn để làm điều này là với các gói setuptools và pkg_resource.

Bạn có thể bố trí gói của mình theo cấu trúc phân cấp sau và định cấu hình tệp thiết lập gói để chỉ ra tài nguyên dữ liệu của bạn, theo liên kết này:

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

Sau đó, bạn có thể tìm lại và sử dụng các tệp đó bằng pkg_resource, theo liên kết này:

http://peak.telecommunity.com/DevCenter/PkgResource#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
Sẽ không pkg_resources tạo ra một sự phụ thuộc thời gian chạy trên setuptools ? Ví dụ: tôi phân phối lại gói Debian, vậy tại sao tôi chỉ phụ thuộc vào python-setuptoolsđiều đó? Cho đến nay __file__hoạt động tốt cho tôi.
mlt

4
Tại sao điều này tốt hơn: Lớp ResourceManager cung cấp quyền truy cập thống nhất vào các tài nguyên gói, cho dù các tài nguyên đó tồn tại dưới dạng tệp và thư mục hoặc được nén trong một kho lưu trữ thuộc loại nào đó
vrdhn

4
Đề nghị rực rỡ, cảm ơn. Tôi đã triển khai một tệp chuẩn mở bằng cách sử dụngfrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
háo hức

5
Làm thế nào điều này sẽ làm việc để sử dụng gói khi nó không được cài đặt? Ý tôi chỉ là thử nghiệm tại địa phương
Claudiu

11
Trong python 3.7, importlib.resourcesthay thế pkg_resourcescho mục đích này (vì vấn đề hiệu suất).
benjimin

13

Để cung cấp một giải pháp làm việc ngày hôm nay. Chắc chắn sử dụng API này để không phát minh lại tất cả các bánh xe.

Một tên tệp hệ thống tập tin thực sự là cần thiết. Trứng đã nén sẽ được trích xuất vào thư mục bộ đệm:

from pkg_resources import resource_filename, Requirement

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

Trả về một đối tượng giống như tệp có thể đọc được cho tài nguyên đã chỉ định; nó có thể là một tập tin thực tế, StringIO hoặc một số đối tượng tương tự. Luồng ở chế độ nhị phân của NỀN TẢNG, theo nghĩa là bất kỳ byte nào trong tài nguyên sẽ được đọc nguyên trạng.

from pkg_resources import resource_stream, Requirement

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

Khám phá gói và truy cập tài nguyên bằng pkg_resource


10

Thường không có điểm nào trong việc đưa ra câu trả lời rằng chi tiết mã không hoạt động như vậy, nhưng tôi tin rằng đây là một ngoại lệ. Python 3.7 được thêm vào importlib.resourcesđược cho là để thay thế pkg_resources. Nó sẽ hoạt động để truy cập các tệp trong các gói không có dấu gạch chéo trong tên của chúng, tức là

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

tức là bạn có thể truy cập data2.txtgói bên trong foochẳng hạn

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

nhưng nó sẽ thất bại với một ngoại lệ cho

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

Điều này không thể được sửa chữa trừ khi đặt __init__.pyvào datavà sau đó sử dụng nó như một gói:

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

Lý do cho hành vi này là "đó là do thiết kế" ; nhưng thiết kế có thể thay đổi ...


Bạn có một liên kết tốt hơn cho "đó là theo thiết kế" so với video youtube - tốt nhất là một video có văn bản?
gerrit

@gerrit cái thứ 2 không chứa văn bản. "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

Bạn cần một tên cho toàn bộ mô-đun của bạn, cây thư mục được cung cấp không liệt kê chi tiết đó, đối với tôi điều này đã hoạt động:

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

Đáng chú ý là setuptools không xuất hiện để phân giải các tệp dựa trên khớp tên với các tệp dữ liệu được đóng gói, vì vậy bạn có phải bao gồm data/tiền tố khá nhiều không có vấn đề gì. Bạn có thể sử dụng os.path.join('data', 'data.txt)nếu bạn cần các dấu tách thư mục thay thế, Nói chung tôi thấy không có vấn đề tương thích với các dấu tách thư mục kiểu unix được mã hóa cứng.


docs.python.org/3.6/distutils/... > Lưu ý rằng bất kỳ tên đường dẫn (các file hoặc thư mục) cung cấp trong kịch bản thiết lập nên được viết theo quy ước Unix, tức là dấu gạch chéo tách biệt. Các Distutils sẽ đảm nhiệm việc chuyển đổi đại diện trung lập nền tảng này thành bất cứ điều gì phù hợp trên nền tảng hiện tại của bạn trước khi thực sự sử dụng tên đường dẫn. Điều này làm cho tập lệnh thiết lập của bạn có thể di động trên các hệ điều hành, tất nhiên đó là một trong những mục tiêu chính của Distutils. Theo tinh thần này, tất cả các tên đường dẫn trong tài liệu này được phân cách bằng dấu gạch chéo.
changyuheng

6

Tôi nghĩ rằng tôi đã săn lùng một câu trả lời.

Tôi tạo một mô-đun data_path.py, mà tôi nhập vào các mô-đun khác có chứa:

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

Và sau đó tôi mở tất cả các tập tin của mình với

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

2
Điều này sẽ không hoạt động khi tài nguyên nằm trong một bản phân phối lưu trữ (chẳng hạn như một quả trứng được nén). Thích một cái gì đó như thế:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis

@ankostis setuptools đủ thông minh để trích xuất kho lưu trữ nếu phát hiện ra rằng bạn đã sử dụng __file__ở đâu đó. Trong trường hợp của tôi, tôi sử dụng một thư viện thực sự muốn đường dẫn và không phải luồng. Tất nhiên tôi có thể tạm thời ghi các tệp vào đĩa nhưng vì lười nên tôi chỉ sử dụng tính năng của setuptools.
letmaik
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.