在Python项目中管理资源
我有一个Python项目,里面用到了很多非代码文件。目前这些文件都是图片,但将来我可能还会用到其他类型的文件。有什么好的方法来存储和引用这些文件呢?
我考虑过在主目录下创建一个“resources”文件夹,但这样有个问题;我的项目中有一些子包也需要用到这些图片。如果这样存储,就会导致文件之间的紧密关联,这样不好。
另外,我还需要一种方法来访问这些文件,不管我当前在哪个目录下都能用。
4 个回答
你可以在每个需要的子包里单独创建一个“资源”文件夹,然后使用 os.path
函数通过子包的 __file__
值来访问这些资源。为了说明我的意思,我在三个地方创建了以下的 __init__.py
文件:
c:\temp\topp (top-level package) c:\temp\topp\sub1 (subpackage 1) c:\temp\topp\sub2 (subpackage 2)
这是 __init__.py
文件:
import os.path
resource_path = os.path.join(os.path.split(__file__)[0], "resources")
print resource_path
在 c:\temp\work 目录下,我创建了一个应用程序,叫做 topapp.py,内容如下:
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 文档。
现在有了一种新的方法来处理这个问题,那就是使用 importlib
。如果你使用的是3.7之前的Python版本,可以添加一个叫 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')
你可能想用 pkg_resources
这个库,它是和 setuptools
一起提供的。
举个例子,我快速做了一个小包 "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
这样的引用就无法解析。