Một khúc dạo đầu:
Trước khi bạn có thể lo lắng về việc đọc các tệp tài nguyên, bước đầu tiên là đảm bảo rằng các tệp dữ liệu đang được đóng gói vào bản phân phối của bạn ngay từ đầu - thật dễ dàng để đọc chúng trực tiếp từ cây nguồn, nhưng phần quan trọng là làm đảm bảo các tệp tài nguyên này có thể truy cập được từ mã trong gói đã cài đặt .
Cấu trúc dự án của bạn như thế này, đặt các tệp dữ liệu vào một thư mục con trong gói:
.
├── package
│ ├── __init__.py
│ ├── templates
│ │ └── temp_file
│ ├── mymodule1.py
│ └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py
Bạn nên vượt qua include_package_data=True
trong setup()
cuộc gọi. Tệp kê khai chỉ cần thiết nếu bạn muốn sử dụng setuptools / distutils và xây dựng các bản phân phối nguồn. Để đảm bảo templates/temp_file
cấu trúc dự án mẫu này được đóng gói, hãy thêm một dòng như thế này vào tệp kê khai:
recursive-include package *
Lưu ý cơ bản về lịch sử: Không cần sử dụng tệp kê khai cho các phần mềm phụ trợ xây dựng hiện đại như flit, thơ, tệp này sẽ bao gồm các tệp dữ liệu gói theo mặc định. Vì vậy, nếu bạn đang sử dụng pyproject.toml
và bạn không có setup.py
tệp thì bạn có thể bỏ qua tất cả nội dung về MANIFEST.in
.
Bây giờ, với cách đóng gói, chuyển sang phần đọc ...
Sự giới thiệu:
Sử dụng các pkgutil
API thư viện tiêu chuẩn . Nó sẽ trông như thế này trong mã thư viện:
# within package/mymodule1.py, for example
import pkgutil
data = pkgutil.get_data(__name__, "templates/temp_file")
print("data:", repr(data))
text = pkgutil.get_data(__name__, "templates/temp_file").decode()
print("text:", repr(text))
Nó hoạt động trong khóa kéo. Nó hoạt động trên Python 2 và Python 3. Nó không yêu cầu phụ thuộc của bên thứ ba. Tôi thực sự không biết về bất kỳ nhược điểm nào (nếu bạn có, hãy bình luận về câu trả lời).
Những cách xấu để tránh:
Cách xấu # 1: sử dụng đường dẫn tương đối từ tệp nguồn
Đây hiện là câu trả lời được chấp nhận. Tốt nhất, nó trông giống như sau:
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))
Có gì sai với điều đó? Giả định rằng bạn có sẵn tệp và thư mục con là không đúng. Cách tiếp cận này không hoạt động nếu thực thi mã được đóng gói trong zip hoặc bánh xe và nó có thể nằm ngoài tầm kiểm soát của người dùng cho dù gói của bạn có được giải nén vào hệ thống tệp hay không.
Cách kém # 2: sử dụng API pkg_resources
Điều này được mô tả trong câu trả lời được bình chọn nhiều nhất. Nó trông giống như sau:
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
Có gì sai với điều đó? Nó cho biết thêm một thời gian chạy phụ thuộc vào setuptools , mà tốt nhất là nên có một cài đặt phụ thuộc thời gian mà thôi. Việc nhập và sử dụng pkg_resources
có thể trở nên thực sự chậm, vì mã tạo ra một tập hợp hoạt động của tất cả các gói đã cài đặt, mặc dù bạn chỉ quan tâm đến tài nguyên gói của riêng mình . Đó không phải là vấn đề lớn tại thời điểm cài đặt (vì quá trình cài đặt chỉ diễn ra một lần), nhưng nó rất tệ khi chạy.
Cách tồi # 3: sử dụng API importlib.resources
Đây hiện là đề xuất trong câu trả lời được bình chọn nhiều nhất. Đó là một bổ sung thư viện tiêu chuẩn gần đây ( mới trong Python 3.7 ), nhưng cũng có sẵn một cổng hỗ trợ. Nó trông như thế này:
try:
from importlib.resources import read_binary
from importlib.resources import read_text
except ImportError:
# Python 2.x backport
from importlib_resources import read_binary
from importlib_resources import read_text
data = read_binary("package.templates", "temp_file")
print("data", repr(data))
text = read_text("package.templates", "temp_file")
print("text", repr(text))
Có gì sai với điều đó? Chà, thật không may, nó vẫn chưa hoạt động ... Đây vẫn là một API chưa hoàn chỉnh, việc sử dụng importlib.resources
sẽ yêu cầu bạn thêm một tệp trống templates/__init__.py
để các tệp dữ liệu sẽ nằm trong một gói con chứ không phải trong một thư mục con. Nó cũng sẽ hiển thị package/templates
thư mục con như một package.templates
gói con có thể nhập theo đúng nghĩa của nó. Nếu đó không phải là vấn đề lớn và nó không làm phiền bạn, thì bạn có thể tiếp tục và thêm __init__.py
tệp vào đó và sử dụng hệ thống nhập để truy cập tài nguyên. Tuy nhiên, trong khi ở đó, bạn cũng có thể tạo nó thành một my_resources.py
tệp thay thế và chỉ cần xác định một số byte hoặc biến chuỗi trong mô-đun, sau đó nhập chúng bằng mã Python. Đó là hệ thống nhập khẩu đang thực hiện công việc nặng nhọc ở đây.
Dự án ví dụ:
Tôi đã tạo một dự án ví dụ trên github và tải lên trên PyPI , trình bày tất cả bốn cách tiếp cận được thảo luận ở trên. Hãy dùng thử với:
$ pip install resources-example
$ resources-example
Xem https://github.com/wimglenn/resources-example để biết thêm thông tin.