我有一个Python项目,其中使用了许多非代码文件。目前这些文件都是图片,但未来可能会使用其他类型的文件。如何存储和引用这些文件才是最佳方案?
我考虑只在主目录中创建一个名为“resources”的文件夹,但存在一个问题:我的一些子包中使用了这些图像。以这种方式存储这些图像将导致耦合,这是一个劣势。
另外,我需要一种独立于当前目录的访问这些文件的方法。
我有一个Python项目,其中使用了许多非代码文件。目前这些文件都是图片,但未来可能会使用其他类型的文件。如何存储和引用这些文件才是最佳方案?
我考虑只在主目录中创建一个名为“resources”的文件夹,但存在一个问题:我的一些子包中使用了这些图像。以这种方式存储这些图像将导致耦合,这是一个劣势。
另外,我需要一种独立于当前目录的访问这些文件的方法。
setuptools
一起提供的 pkg_resources
库。"proj"
来说明我将使用的资源组织方案:
proj/setup.py proj/proj/__init__.py proj/proj/code.py proj/proj/resources/__init__.py proj/proj/resources/images/__init__.py proj/proj/resources/images/pic1.png proj/proj/resources/images/pic2.png请注意,我将所有资源保留在单独的子包中。
"code.py"
显示了如何使用 pkg_resources
引用资源对象:from pkg_resources import resource_string, resource_listdir
# Itemize data files under proj/resources/images:
print resource_listdir('proj.resources.images', '')
# Get the data file bytes:
print resource_string('proj.resources.images', 'pic2.png').encode('base64')
如果运行它,您将得到:
['__init__.py', '__init__.pyc', 'pic1.png', 'pic2.png'] iVBORw0KGgoAAAANSUhE ...如果您需要将资源视为文件对象,则使用
resource_stream()
。proj.resources.images
,在这种情况下。"setup.py"
:#!/usr/bin/env python
from setuptools import setup, find_packages
setup(name='proj',
packages=find_packages(),
package_data={'': ['*.png']})
注意:为了在未安装软件包的情况下“本地”测试,您必须从具有setup.py
的目录中调用测试脚本。如果您位于与code.py
相同的目录中,则Python不会知道proj
包。因此,像proj.resources
这样的内容将无法解析。
现在做这件事的新方法是使用importlib
。对于Python版本低于3.7的情况,您可以添加一个依赖项importlib_resources
并执行以下操作:
from importlib_resources import files
def get_resource(module: str, name: str) -> str:
"""Load a textual resource file."""
return files(module).joinpath(name).read_text(encoding="utf-8")
如果您的资源位于foo/resources
子模块内,那么您将像这样使用get_resource
。resource_text = get_resource('foo.resources', 'myresource')
os.path
函数从您的子包的__file__
值中获取这些内容。为了说明我的意思,我在三个位置创建了以下__init__.py
文件:
c:\temp\topp (顶层包) c:\temp\topp\sub1 (子包1) c:\temp\topp\sub2 (子包2)
下面是__init__.py
文件:
import os.path
resource_path = os.path.join(os.path.split(__file__)[0], "resources")
print resource_path
import topp
import topp.sub1
import topp.sub2
这表示应用程序使用topp
包和子包。然后我运行它:
C:\temp\work>topapp Traceback (most recent call last): File "C:\temp\work\topapp.py", line 1, in import topp ImportError: No module named topp
这是预期的。我们设置PYTHONPATH以模拟将我们的包加入到路径中:
C:\temp\work>set PYTHONPATH=c:\temp C:\temp\work>topapp c:\temp\topp\resources c:\temp\topp\sub1\resources c:\temp\topp\sub2\resources
正如您所看到的,资源路径正确地解析为路径上实际(子)包的位置。
更新: 这里是相关的py2exe文档。
__file__
不起作用的事实。 - Ram Rachum在pycon2009上,有一个关于distutils和setuptools的演示。你可以在这里找到所有的视频。
在这些视频中,他们描述了如何在您的软件包中包含静态资源。我相信这是在第2部分中。
使用setuptools,您可以定义依赖项,这将允许您拥有使用第三方软件包资源的两个软件包。
Setuptools还为您提供了一种标准的访问这些资源的方式,并允许您在软件包内使用相对路径,从而消除了担心软件包安装位置的需要。
distutils
方式(标准):文档将访问资源文件留给读者自己练习(可能是因为他们认为相对于__file__
的路径操作就足够了)。2)setuptools
方式(distutils
的超集),如上所述。 - Pavel Repinresource_string
的输出,应该使用decode
而不是encode
,不是吗? - archeezeeresource_string
返回pic2.png
的原始字节表示,如果你想要打印它的话,这并不理想 :) 因此需要进行 base64 编码调用。 - Pavel Repinresources
必须 是proj
的子目录,否则当有人使用 pip 安装我的项目时,我将用resources
污染他们的包空间(import resources
将获取我的资源目录)。非常烦人的是,由于 Python 已经将其与完全不相关的源代码包概念耦合在一起,所以我放置数据的位置必须受到限制。 - Jack M