这是通过 Python 脚本访问 to/packaged 附近数据的批准方式吗?

Is this the approved way to acess data adjacent to/packaged with a Python script?

我有一个 Python 脚本需要一些数据存储在一个文件中,该文件始终与脚本位于同一位置。我有一个用于脚本的 setup.py,我想确保它可以在各种环境中通过 pip 安装,并且可以在必要时转换为独立的可执行文件。

目前 运行s 的脚本 Python 2.7 和 Python 3.3 或更高版本(虽然我没有 3.3 的测试环境所以我不能确定那个)。

我想出了这个方法来获取数据。这个脚本不是带有 __init__.py 或任何东西的模块目录的一部分,它只是一个独立的文件,如果直接 运行 和 python 就可以工作,但也有一个入口点定义在setup.py 文件。都是一个文件。这是正确的方法吗?

def fetch_wordlist():
    wordlist = 'wordlist.txt'
    try:
        import importlib.resources as res
        return res.read_binary(__file__, wordlist)
    except ImportError:
        pass
    try:
        import pkg_resources as resources
        req = resources.Requirement.parse('makepw')
        wordlist = resources.resource_filename(req, wordlist)
    except ImportError:
        import os.path
        wordlist = os.path.join(os.path.dirname(__file__), wordlist)
    with open(wordlist, 'rb') as f:
        return f.read()

这看起来复杂得离谱。此外,它似乎以我不满意的方式依赖包管理系统。该脚本不再有效,除非它是 pip 安装的,而且这似乎也不可取。

您说得对,您读取文件的方法有点不必要地复杂。除非您有真正特定的理由使用 importlibpkg_resources 模块,否则它相当简单。

import os

def fetch_wordlist():
    if not os.path.exists('wordlist.txt'):
        raise FileNotFoundError

    with open('wordlist.txt', 'rb') as wordlist:
        return wordlist.read()

您没有提供太多关于您的脚本的信息,所以我不能评论为什么它不能工作,除非它是使用 pip 安装的。我的最佳猜测:您的脚本可能打包到 python 包中。

文件系统中的资源

读取与 python 脚本相邻的文件的标准方法是:

a) 如果你有 python>=3.4,我建议你使用 pathlib 模块,像这样:

from pathlib import Path


def fetch_wordlist(filename="wordlist.txt"):
    return (Path(__file__).parent / filename).read_text()


if __name__ == '__main__':
    print(fetch_wordlist())

b) 如果您仍在使用 python 版本 <3.4,或者您仍想使用旧的 os.path 模块,您应该这样做:

import os


def fetch_wordlist(filename="wordlist.txt"):
    with open(os.path.join(os.path.dirname(__file__), filename)) as f:
        return f.read()


if __name__ == '__main__':
    print(fetch_wordlist())

此外,我建议您在外部调用者中捕获异常,上述方法是读取 python 中文件的标准方法,因此您不需要将它们包装在 [=14= 之类的函数中],否则的话,读取python中的文件是一个"atomic"操作。

现在,您可能会使用 cx_freezepyinstallersimilars 等冻结器冻结您的程序...在这种情况下,您需要检测到,这里有一个简单的方法来检查它:

a) 使用 os.path:

if getattr(sys, 'frozen', False):
    app_path = os.path.dirname(sys.executable)
elif __file__:
    app_path = os.path.dirname(__file__)

b) 使用 pathlib:

if getattr(sys, 'frozen', False):
    app_path = Path(sys.executable).parent
elif __file__:
    app_path = Path(__file__).parent

压缩文件中的资源

如果代码存在于文件系统中,上述解决方案将起作用,但如果包存在于 zip 文件中,则上述解决方案将不起作用,当发生这种情况时,您可以使用 importlib.resources (new in version 3.7) or pkg_resources 组合,因为已经在问题中展示了(或者你可以用一些助手来解决)或者你可以使用一个很好的第三方库 importlib_resources 应该与旧的和现代的 python 版本一起工作:

特别针对您的特定问题,我建议您看看这个 https://importlib-resources.readthedocs.io/en/latest/using.html#file-system-or-zip-file

如果您想知道该库在幕后做了什么,因为您不愿意安装任何第 3 方库,您可以找到 py2 here and py3 here 的代码,以防您想要获得相关信息针对您的特定问题的位

我要冒险做一个假设,因为它可能会大大简化您的问题。我可以想象的唯一方式,你可以声称这个数据是 "stored in a file that will always be in the same location as the script" 是因为你创建了这个数据,一次,并将它放在源代码目录中的一个文件中。即使此数据是二进制数据,您是否考虑过将数据作为 python 文件中的文字字节字符串,然后像导入其他任何东西一样简单地导入它?