在Python项目中管理资源

71 投票
4 回答
58763 浏览
提问于 2025-04-15 14:09

我有一个Python项目,里面用到了很多非代码文件。目前这些文件都是图片,但将来我可能还会用到其他类型的文件。有什么好的方法来存储和引用这些文件呢?

我考虑过在主目录下创建一个“resources”文件夹,但这样有个问题;我的项目中有一些子包也需要用到这些图片。如果这样存储,就会导致文件之间的紧密关联,这样不好。

另外,我还需要一种方法来访问这些文件,不管我当前在哪个目录下都能用。

4 个回答

6

你可以在每个需要的子包里单独创建一个“资源”文件夹,然后使用 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 文档。

19

现在有了一种新的方法来处理这个问题,那就是使用 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')
68

你可能想用 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 这样的引用就无法解析。

撰写回答